summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/agent.cgi22
-rwxr-xr-xhttemplate/browse/agent_type.cgi4
-rwxr-xr-xhttemplate/browse/cust_main_county.cgi47
-rw-r--r--httemplate/browse/part_device.html27
-rwxr-xr-xhttemplate/browse/part_pkg.cgi98
-rw-r--r--httemplate/browse/part_pkg_report_option.html28
-rw-r--r--httemplate/browse/part_pkg_taxclass.html27
-rwxr-xr-xhttemplate/browse/part_svc.cgi27
-rw-r--r--httemplate/browse/payment_gateway.html4
-rw-r--r--httemplate/browse/pkg_category.html6
-rw-r--r--httemplate/browse/rate_region.html59
-rwxr-xr-xhttemplate/browse/svc_acct_pop.cgi1
-rw-r--r--httemplate/config/config-delete.cgi15
-rw-r--r--httemplate/config/config-image.cgi5
-rw-r--r--httemplate/config/config-process.cgi34
-rw-r--r--httemplate/config/config-view.cgi247
-rw-r--r--httemplate/config/config.cgi30
-rw-r--r--httemplate/docs/about.html8
-rw-r--r--httemplate/docs/credits.html17
-rw-r--r--httemplate/docs/license.html10
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi6
-rwxr-xr-xhttemplate/edit/agent.cgi10
-rwxr-xr-xhttemplate/edit/agent_type.cgi2
-rwxr-xr-xhttemplate/edit/cust_credit.cgi10
-rwxr-xr-xhttemplate/edit/cust_main.cgi701
-rw-r--r--httemplate/edit/cust_main/billing.html194
-rw-r--r--httemplate/edit/cust_main/birthdate.html15
-rw-r--r--httemplate/edit/cust_main/bottomfixup.html19
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js398
-rw-r--r--httemplate/edit/cust_main/choose_tax_location.html10
-rw-r--r--httemplate/edit/cust_main/contact.html7
-rw-r--r--httemplate/edit/cust_main/first_pkg.html55
-rw-r--r--httemplate/edit/cust_main/first_pkg/select-part_pkg.html170
-rw-r--r--httemplate/edit/cust_main/first_pkg/svc_acct.html88
-rw-r--r--httemplate/edit/cust_main/first_pkg/svc_phone.html82
-rw-r--r--httemplate/edit/cust_main/select-domain.html67
-rw-r--r--httemplate/edit/cust_main/top_misc.html97
-rwxr-xr-xhttemplate/edit/cust_main_attach.cgi59
-rwxr-xr-xhttemplate/edit/cust_pay.cgi14
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi4
-rw-r--r--httemplate/edit/cust_tax_adjustment.html102
-rw-r--r--httemplate/edit/elements/edit.html160
-rw-r--r--httemplate/edit/elements/svc_Common.html28
-rwxr-xr-xhttemplate/edit/part_bill_event.cgi6
-rw-r--r--httemplate/edit/part_device.html16
-rw-r--r--httemplate/edit/part_export.cgi23
-rwxr-xr-xhttemplate/edit/part_pkg.cgi95
-rw-r--r--httemplate/edit/part_pkg_report_option.html23
-rw-r--r--httemplate/edit/part_pkg_taxclass.html43
-rwxr-xr-xhttemplate/edit/part_svc.cgi34
-rw-r--r--httemplate/edit/payment_gateway.html244
-rw-r--r--httemplate/edit/phone_device.html37
-rw-r--r--httemplate/edit/pkg_category.html2
-rw-r--r--httemplate/edit/prepay_credit.cgi12
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi11
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi152
-rw-r--r--httemplate/edit/process/cust_main_attach.cgi99
-rwxr-xr-xhttemplate/edit/process/cust_main_county-collapse.cgi67
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi6
-rw-r--r--httemplate/edit/process/cust_tax_adjustment.html41
-rwxr-xr-xhttemplate/edit/process/domreg.cgi62
-rw-r--r--httemplate/edit/process/elements/ApplicationCommon.html2
-rw-r--r--httemplate/edit/process/part_device.html11
-rw-r--r--httemplate/edit/process/part_export.cgi3
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi44
-rw-r--r--httemplate/edit/process/part_pkg_report_option.html11
-rw-r--r--httemplate/edit/process/part_pkg_taxclass.html58
-rw-r--r--httemplate/edit/process/payment_gateway.html39
-rw-r--r--httemplate/edit/process/phone_device.html18
-rw-r--r--httemplate/edit/process/quick-charge.cgi8
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi13
-rwxr-xr-xhttemplate/edit/process/svc_domain.cgi4
-rw-r--r--httemplate/edit/quick-charge.html91
-rw-r--r--httemplate/edit/reg_code.cgi2
-rwxr-xr-xhttemplate/edit/router.cgi10
-rwxr-xr-xhttemplate/edit/svc_acct.cgi2
-rw-r--r--httemplate/edit/svc_broadband.cgi5
-rwxr-xr-xhttemplate/edit/svc_domain.cgi42
-rw-r--r--httemplate/edit/svc_www.cgi2
-rw-r--r--httemplate/elements/about_freeside.html19
-rw-r--r--httemplate/elements/about_rt.html13
-rw-r--r--httemplate/elements/checkbox.html19
-rw-r--r--httemplate/elements/checkboxes.html10
-rw-r--r--httemplate/elements/file-upload.html2
-rw-r--r--httemplate/elements/header-popup.html33
-rw-r--r--httemplate/elements/header.html175
-rw-r--r--httemplate/elements/location.html50
-rw-r--r--httemplate/elements/menu.html237
-rw-r--r--httemplate/elements/menuarrow.gifbin0 -> 68 bytes
-rw-r--r--httemplate/elements/menubar.html118
-rw-r--r--httemplate/elements/popup_link-cust_main.html2
-rw-r--r--httemplate/elements/popup_link-ping.html30
-rw-r--r--httemplate/elements/popup_link.html8
-rw-r--r--httemplate/elements/progress-popup.html2
-rw-r--r--httemplate/elements/select-county.html6
-rw-r--r--httemplate/elements/select-cust-part_pkg.html12
-rw-r--r--httemplate/elements/select-cust-pkg_class.html12
-rw-r--r--httemplate/elements/select-cust_main-status.html7
-rw-r--r--httemplate/elements/select-cust_pkg-balances.html32
-rw-r--r--httemplate/elements/select-cust_pkg-status.html7
-rw-r--r--httemplate/elements/select-did.html5
-rw-r--r--httemplate/elements/select-domain.html2
-rw-r--r--httemplate/elements/select-part_pkg.html14
-rw-r--r--httemplate/elements/select-part_svc.html18
-rw-r--r--httemplate/elements/select-svc_acct-domain.html46
-rw-r--r--httemplate/elements/select-table.html22
-rw-r--r--httemplate/elements/select-taxclass.html8
-rw-r--r--httemplate/elements/select-terms.html26
-rw-r--r--httemplate/elements/selectlayers.html42
-rw-r--r--httemplate/elements/tr-checkbox.html8
-rw-r--r--httemplate/elements/tr-input-date-field.html27
-rw-r--r--httemplate/elements/tr-justtitle.html2
-rw-r--r--httemplate/elements/tr-select-cust-part_pkg.html107
-rw-r--r--httemplate/elements/tr-select-cust_pkg-balances.html31
-rw-r--r--httemplate/elements/tr-select-did.html20
-rw-r--r--httemplate/elements/tr-select-part_svc.html7
-rw-r--r--httemplate/elements/tr-select-pkg_class.html6
-rw-r--r--httemplate/elements/tr-select-svc_acct-domain.html34
-rw-r--r--httemplate/elements/tr-select-taxclass.html10
-rw-r--r--httemplate/elements/tr-selectmultiple-part_pkg.html9
-rw-r--r--httemplate/elements/tr-textarea.html30
-rw-r--r--httemplate/elements/tr-title.html7
-rw-r--r--httemplate/elements/xmenu.css2
-rw-r--r--httemplate/elements/xmenu.top.css2
-rw-r--r--httemplate/graph/cust_bill_pkg.cgi96
-rw-r--r--httemplate/graph/cust_bill_pkg_detail.cgi137
-rw-r--r--httemplate/graph/report_cust_bill_pkg.html11
-rw-r--r--httemplate/graph/report_cust_bill_pkg_detail.html48
-rw-r--r--httemplate/images/gray-black-side.pngbin0 -> 213 bytes
-rwxr-xr-xhttemplate/misc/bulk_change_pkg.cgi25
-rwxr-xr-xhttemplate/misc/cancel_pkg.html2
-rwxr-xr-xhttemplate/misc/change_pkg.cgi22
-rw-r--r--httemplate/misc/cust-part_pkg.cgi29
-rw-r--r--httemplate/misc/cust_main-import.cgi2
-rwxr-xr-xhttemplate/misc/delay_susp_pkg.html2
-rw-r--r--httemplate/misc/delete-cust_bill.html21
-rwxr-xr-xhttemplate/misc/delete-phone_device.html23
-rw-r--r--httemplate/misc/download-batch.cgi200
-rw-r--r--httemplate/misc/email-customers.html2
-rwxr-xr-xhttemplate/misc/link.cgi1
-rw-r--r--httemplate/misc/meta-import.cgi2
-rw-r--r--httemplate/misc/order_pkg.html49
-rw-r--r--httemplate/misc/part_device-import.html53
-rw-r--r--httemplate/misc/part_svc-columns.cgi13
-rw-r--r--httemplate/misc/payment.cgi152
-rw-r--r--httemplate/misc/ping.html102
-rwxr-xr-xhttemplate/misc/process/link.cgi10
-rw-r--r--httemplate/misc/process/part_device-import.html9
-rw-r--r--httemplate/misc/process/payment.cgi23
-rw-r--r--httemplate/misc/process/rate_edit_excel.html10
-rwxr-xr-xhttemplate/misc/process/recharge_svc.html103
-rw-r--r--httemplate/misc/process/tax-fetch_and_import.cgi9
-rw-r--r--httemplate/misc/process/tax-fetch_and_replace.cgi9
-rw-r--r--httemplate/misc/process/tax-import.cgi2
-rw-r--r--httemplate/misc/rate_edit_excel.html61
-rw-r--r--httemplate/misc/send-invoice.cgi30
-rwxr-xr-xhttemplate/misc/send-statement.cgi28
-rw-r--r--httemplate/misc/states.cgi12
-rw-r--r--httemplate/misc/tax-fetch_and_import.cgi48
-rw-r--r--httemplate/misc/tax-fetch_and_replace.cgi48
-rw-r--r--httemplate/misc/tax-import.cgi8
-rw-r--r--httemplate/misc/xmlhttp-cust_main-address_standardize.html5
-rw-r--r--httemplate/misc/xmlhttp-cust_main-censustract.html105
-rw-r--r--httemplate/misc/xmlhttp-ping.html20
-rw-r--r--httemplate/pref/pref-process.html113
-rw-r--r--httemplate/pref/pref.html32
-rwxr-xr-xhttemplate/search/477.html155
-rw-r--r--httemplate/search/cdr.html156
-rw-r--r--httemplate/search/cust_bill_pkg.cgi242
-rw-r--r--httemplate/search/cust_event.html99
-rwxr-xr-xhttemplate/search/cust_main.cgi15
-rwxr-xr-xhttemplate/search/cust_main.html9
-rwxr-xr-xhttemplate/search/cust_pay_batch.cgi5
-rwxr-xr-xhttemplate/search/cust_pay_void.html13
-rwxr-xr-xhttemplate/search/cust_pkg.cgi61
-rw-r--r--httemplate/search/cust_tax_adjustment.html54
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html219
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html12
-rw-r--r--httemplate/search/elements/search-csv.html48
-rw-r--r--httemplate/search/elements/search-html.html454
-rw-r--r--httemplate/search/elements/search-xls.html83
-rw-r--r--httemplate/search/elements/search.html606
-rw-r--r--httemplate/search/reg_code.html2
-rwxr-xr-xhttemplate/search/report_477.html64
-rw-r--r--httemplate/search/report_cdr.html90
-rwxr-xr-xhttemplate/search/report_cust_main.html11
-rw-r--r--httemplate/search/report_cust_pay.html43
-rwxr-xr-xhttemplate/search/report_cust_pkg.html43
-rwxr-xr-xhttemplate/search/report_newtax.cgi105
-rw-r--r--httemplate/search/report_prepaid_income.cgi38
-rw-r--r--httemplate/search/report_prepaid_income.html70
-rwxr-xr-xhttemplate/search/report_receivables.cgi168
-rwxr-xr-xhttemplate/search/report_receivables.html8
-rw-r--r--httemplate/search/report_svc_phone.html32
-rwxr-xr-xhttemplate/search/report_tax.cgi162
-rwxr-xr-xhttemplate/search/report_tax.html36
-rwxr-xr-xhttemplate/search/report_unapplied_cust_pay.html41
-rwxr-xr-xhttemplate/search/svc_broadband.cgi4
-rwxr-xr-xhttemplate/search/svc_external.cgi286
-rw-r--r--httemplate/search/svc_phone.cgi71
-rwxr-xr-xhttemplate/search/unapplied_cust_pay.html28
-rw-r--r--httemplate/view/attachment.html16
-rwxr-xr-xhttemplate/view/cust_bill-logo.cgi2
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi22
-rwxr-xr-xhttemplate/view/cust_bill-ps.cgi21
-rwxr-xr-xhttemplate/view/cust_bill.cgi73
-rwxr-xr-xhttemplate/view/cust_main.cgi134
-rwxr-xr-xhttemplate/view/cust_main/attachments.html151
-rw-r--r--httemplate/view/cust_main/billing.html37
-rw-r--r--httemplate/view/cust_main/change_history.html302
-rw-r--r--httemplate/view/cust_main/misc.html19
-rw-r--r--httemplate/view/cust_main/one_time_charge_link.html91
-rwxr-xr-xhttemplate/view/cust_main/packages.html214
-rw-r--r--httemplate/view/cust_main/packages/package.html23
-rw-r--r--httemplate/view/cust_main/packages/services.html28
-rw-r--r--httemplate/view/cust_main/packages/status.html86
-rw-r--r--httemplate/view/cust_main/payment_history.html51
-rw-r--r--httemplate/view/cust_main/payment_history/credit.html14
-rw-r--r--httemplate/view/cust_main/payment_history/invoice.html19
-rw-r--r--httemplate/view/cust_main/payment_history/payment.html14
-rw-r--r--httemplate/view/cust_main/payment_history/statement.html34
-rw-r--r--httemplate/view/cust_main/payment_history/voided_payment.html27
-rw-r--r--httemplate/view/cust_main/tickets.html3
-rw-r--r--httemplate/view/cust_pay.html41
-rw-r--r--httemplate/view/cust_pay_void.html1
-rwxr-xr-xhttemplate/view/cust_statement-pdf.cgi28
-rwxr-xr-xhttemplate/view/cust_statement.html79
-rw-r--r--httemplate/view/elements/svc_Common.html63
-rw-r--r--httemplate/view/svc_Common.html12
-rwxr-xr-xhttemplate/view/svc_acct.cgi2
-rw-r--r--httemplate/view/svc_broadband.cgi59
-rwxr-xr-xhttemplate/view/svc_domain.cgi57
-rw-r--r--httemplate/view/svc_phone.cgi75
233 files changed, 9200 insertions, 3439 deletions
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
index 0a516ed..1c06f1b 100755
--- a/httemplate/browse/agent.cgi
+++ b/httemplate/browse/agent.cgi
@@ -20,8 +20,6 @@ full offerings (via their type).<BR><BR>
% my $bgcolor1 = '#eeeeee';
% my $bgcolor2 = '#ffffff';
% my $bgcolor = '';
-%
-
<TR>
<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH>
@@ -34,18 +32,17 @@ full offerings (via their type).<BR><BR>
<TH CLASS="grid" BGCOLOR="#cccccc">Reports</TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Registration<BR>codes</FONT></TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Prepaid cards</TH>
-% if ( $conf->config('ticket_system') ) {
+% if ( $conf->config('ticket_system') ) {
<TH CLASS="grid" BGCOLOR="#cccccc">Ticketing</TH>
% }
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Configuration Overrides</FONT></TH>
</TR>
-%
+
%# <TH><FONT SIZE=-1>Agent #</FONT></TH>
%# <TH>Agent</TH>
-%
%foreach my $agent ( sort {
% #$a->getfield('agentnum') <=> $b->getfield('agentnum')
% $a->getfield('agent') cmp $b->getfield('agent')
@@ -61,9 +58,6 @@ full offerings (via their type).<BR><BR>
% } else {
% $bgcolor = $bgcolor1;
% }
-%
-%
-
<TR>
@@ -366,7 +360,7 @@ Unused
? ' for '. $override->taxclass. ' only'
: ''
%>
- <FONT SIZE=-1><A HREF="<%$p%>misc/delete-agent_payment_gateway.cgi?<% $override->agentgatewaynum %>">(delete)</A></FONT>
+ <FONT SIZE=-1><A HREF="javascript:areyousure('delete this payment gateway override', '<%$p%>misc/delete-agent_payment_gateway.cgi?<% $override->agentgatewaynum %>')">(delete)</A></FONT>
</TD>
</TR>
% }
@@ -386,7 +380,7 @@ Unused
<TR>
<TD>
- <% $override->name %>&nbsp;<FONT SIZE=-1><A HREF="<%$p%>config/config-delete.cgi?<% $override->confnum %>">(delete)</A></FONT>
+ <% $override->name %>&nbsp;<FONT SIZE=-1><A HREF="javascript:areyousure('delete this configuration override', '<%$p%>config/config-delete.cgi?confnum=<% $override->confnum %>')">(delete)</A></FONT>
</TD>
</TR>
% }
@@ -402,6 +396,14 @@ Unused
</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ function areyousure(what, href) {
+ if ( confirm("Are you sure you want to " + what + "?") == true )
+ window.location.href = href;
+ }
+</SCRIPT>
+
</BODY>
</HTML>
<%init>
diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi
index d64ff18..f07a651 100755
--- a/httemplate/browse/agent_type.cgi
+++ b/httemplate/browse/agent_type.cgi
@@ -44,7 +44,9 @@ my $agent_type = shift;
[
{
#'data' => $part_pkg->pkg. ' - '. $part_pkg->comment,
- 'data' => $type_pkgs->pkg. ' - '. $type_pkgs->comment,
+ 'data' => $type_pkgs->pkg. ' - '.
+ ( $type_pkgs->custom ? '(CUSTOM) ' : '' ).
+ $type_pkgs->comment,
'align' => 'left',
'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart,
},
diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi
index 736d7fd..232e688 100755
--- a/httemplate/browse/cust_main_county.cgi
+++ b/httemplate/browse/cust_main_county.cgi
@@ -23,10 +23,6 @@
'link_onclicks' => \@link_onclicks,
)
%>
-%
-% # <FONT SIZE=-1><A HREF="<% $p %>edit/process/cust_main_county-collapse.cgi?<% $hashref->{taxnum} %>">collapse state</A></FONT>
-% # % }
-%
<%once>
my $conf = new FS::Conf;
@@ -102,6 +98,17 @@ sub expand_link {
'</FONT>';
}
+sub collapse_link {
+ my %param = @_;
+
+ my $taxnum = $param{'row'}->taxnum;
+ my $url = "${p}edit/process/cust_main_county-collapse.cgi?$taxnum";
+ $url = "javascript:collapse_areyousure('$url')";
+
+ qq(<FONT SIZE="-1"><A HREF="$url">$param{'label'}</A></FONT>);
+}
+
+
sub separate_taxclasses_link {
my( $row ) = @_;
my $taxnum = $row->taxnum;
@@ -110,6 +117,8 @@ sub separate_taxclasses_link {
qq!<FONT SIZE="-1"><A HREF="$url">!;
}
+#un-separate taxclasses too
+
</%once>
<%init>
@@ -122,9 +131,18 @@ my $enable_taxclasses = $conf->exists('enable_taxclasses');
my @menubar;
-my $html_init =
- "Click on <u>add states</u> to specify a country's tax rates by state or province.
- <BR>Click on <u>add counties</u> to specify a state's tax rates by county.";
+my $html_init = <<END;
+ <SCRIPT>
+ function collapse_areyousure(href) {
+ if (confirm("Are you sure you want to remove all county tax rates for this state?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ Click on <u>add states</u> to specify a country's tax rates by state or province.
+ <BR>Click on <u>add counties</u> to specify a state's tax rates by county, or <u>remove counties</u> to remove per-county tax rates.
+END
+
$html_init .= "<BR>Click on <u>separate taxclasses</u> to specify taxes per taxclass."
if $enable_taxclasses;
$html_init .= '<BR><BR>';
@@ -360,11 +378,16 @@ my @fields = (
)
)
},
- sub { $_[0]->county || '(all)&nbsp'.
- expand_link( desc => 'Add Counties',
- row => $_[0],
- label => 'add&nbsp;counties',
- )
+ sub { $_[0]->county
+ ? $_[0]->county. '&nbsp'.
+ collapse_link( label=> 'remove&nbsp;counties',
+ row => $_[0],
+ )
+ : '(all)&nbsp'.
+ expand_link( desc => 'Add Counties',
+ row => $_[0],
+ label => 'add&nbsp;counties',
+ );
},
);
diff --git a/httemplate/browse/part_device.html b/httemplate/browse/part_device.html
new file mode 100644
index 0000000..5c8fde3
--- /dev/null
+++ b/httemplate/browse/part_device.html
@@ -0,0 +1,27 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Phone device types',
+ 'name' => 'phone device types',
+ 'menubar' => [ 'Add a new device type' =>
+ $p.'edit/part_device.html',
+ 'Import device types' =>
+ $p.'misc/part_device-import.html',
+ ],
+ 'query' => { 'table' => 'part_device', },
+ 'count_query' => 'SELECT COUNT(*) FROM part_device',
+ 'header' => [ '#', 'Device type' ],
+ 'fields' => [ 'devicepart',
+ 'devicename',
+ ],
+ 'links' => [ $link,
+ $link,
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/part_device.html?", 'devicepart' ];
+
+</%init>
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 801c09f..c6cbb81 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -1,16 +1,17 @@
<% include( 'elements/browse.html',
'title' => 'Package Definitions',
'html_init' => $html_init,
+ 'html_posttotal' => $html_posttotal,
'name' => 'package definitions',
'disableable' => 1,
- 'disabled_statuspos' => 3,
+ 'disabled_statuspos' => 4,
'agent_virt' => 1,
'agent_null_right' => [ $edit, $edit_global ],
'agent_null_right_link' => $edit_global,
- 'agent_pos' => 5,
+ 'agent_pos' => 6,
'query' => { 'select' => $select,
'table' => 'part_pkg',
- 'hashref' => {},
+ 'hashref' => \%hash,
'extra_sql' => $extra_sql,
'order_by' => "ORDER BY $orderby"
},
@@ -41,16 +42,48 @@ my $money_char = $conf->config('money_char') || '$';
my $select = '*';
my $orderby = 'pkgpart';
+my %hash = ();
+my $extra_count = '';
+
if ( $cgi->param('active') ) {
$orderby = 'num_active DESC';
}
-my $extra_sql = '';
+my @where = ();
+
+#if ( $cgi->param('activeONLY') ) {
+# push @where, ' WHERE num_active > 0 '; #XXX doesn't affect count...
+#}
+
+if ( $cgi->param('recurring') ) {
+ $hash{'freq'} = { op=>'!=', value=>'0' };
+ $extra_count = " freq != '0' ";
+}
-unless ( $acl_edit_global ) {
- $extra_sql .= ' WHERE '. FS::part_pkg->curuser_pkgs_sql;
+my $classnum = '';
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ $classnum = $1;
+ push @where, $classnum ? "classnum = $classnum"
+ : "classnum IS NULL";
+}
+$cgi->delete('classnum');
+
+if ( $cgi->param('missing_recur_fee') ) {
+ push @where, "0 = ( SELECT COUNT(*) FROM part_pkg_option
+ WHERE optionname = 'recur_fee'
+ AND part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND CAST ( optionvalue AS NUMERIC ) > 0
+ )";
}
+push @where, FS::part_pkg->curuser_pkgs_sql
+ unless $acl_edit_global;
+
+my $extra_sql = scalar(@where)
+ ? ( scalar(keys %hash) ? ' AND ' : ' WHERE ' ).
+ join( 'AND ', @where)
+ : '';
+
my $agentnums = join(',', $curuser->agentnums);
my $count_cust_pkg = "
SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )
@@ -94,14 +127,49 @@ my $html_init;
!;
#}
+$cgi->param('dummy', 1);
+
+my $filter_change =
+ qq(\n<SCRIPT TYPE="text/javascript">\n).
+ "function filter_change() {".
+ " window.location = '". $cgi->self_url.
+ ";classnum=' + document.getElementById('classnum').options[document.getElementById('classnum').selectedIndex].value".
+ "}".
+ "\n</SCRIPT>\n";
+
+#restore this so pagination works
+$cgi->param('classnum', $classnum) if length($classnum);
+
+my $html_posttotal =
+ "$filter_change\n<BR>( show class: ".
+ include('/elements/select-pkg_class.html',
+ #'curr_value' => $classnum,
+ 'value' => $classnum, #insist on 0 :/
+ 'onchange' => 'filter_change()',
+ 'pre_options' => [ '-1' => 'all',
+ '0' => '(none)', ],
+ 'disable_empty' => 1,
+ ).
+ ' )';
+
+my $recur_toggle = $cgi->param('recurring') ? 'show' : 'hide';
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 );
+
+$html_posttotal .=
+ '( <A HREF="'. $cgi->self_url.'">'. "$recur_toggle one-time charges</A> )";
+
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 ); #put it back
+
# ------
my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ];
-my @header = ( '#', 'Package', 'Comment' );
-my @fields = ( 'pkgpart', 'pkg', 'comment' );
-my $align = 'rll';
-my @links = ( $link, $link, '' );
+my @header = ( '#', 'Package', 'Comment', 'Custom' );
+my @fields = ( 'pkgpart', 'pkg', 'comment',
+ sub{ '<B><FONT COLOR="#0000CC">'.$_[0]->custom.'</FONT></B>' }
+ );
+my $align = 'rllc';
+my @links = ( $link, $link, '', '' );
unless ( 0 ) { #already showing only one class or something?
push @header, 'Class';
@@ -188,9 +256,7 @@ if ( $acl_edit_global ) {
my $typelink = $p. 'edit/agent_type.cgi?';
push @fields, sub { my $part_pkg = shift;
[
- map { warn $_;
- my $agent_type = $_->agent_type;
- warn $agent_type;
+ map { my $agent_type = $_->agent_type;
[
{ 'data' => $agent_type->atype, #escape?
'align' => 'left',
@@ -362,6 +428,10 @@ $align .= 'lrl'; #rr';
# --------
-my $count_query = "SELECT COUNT(*) FROM part_pkg $extra_sql";
+my $count_extra_sql = $extra_sql;
+$count_extra_sql =~ s/^\s*AND /WHERE /i;
+$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";
</%init>
diff --git a/httemplate/browse/part_pkg_report_option.html b/httemplate/browse/part_pkg_report_option.html
new file mode 100644
index 0000000..90540bc
--- /dev/null
+++ b/httemplate/browse/part_pkg_report_option.html
@@ -0,0 +1,28 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Package optional report classes',
+ 'html_init' => $html_init,
+ 'name' => 'package optional report classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'query' => { 'table' => 'part_pkg_report_option',
+ 'hashref' => {},
+ 'extra_sql' => 'ORDER BY name',
+ },
+ 'count_query' => 'SELECT COUNT(*) FROM part_pkg_report_option',
+ 'header' => [ '#', 'Class' ],
+ 'fields' => [ 'num', 'name' ],
+ 'links' => [ $link, $link ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Package optional report classes define groups of packages, for reporting purposes.'.
+ qq!<BR><BR><A HREF="${p}edit/part_pkg_report_option.html"><I>Add a class</I></A><BR><BR>!;
+
+my $link = [ $p.'edit/part_pkg_report_option.html?', 'num' ];
+
+</%init>
diff --git a/httemplate/browse/part_pkg_taxclass.html b/httemplate/browse/part_pkg_taxclass.html
new file mode 100644
index 0000000..04e0e23
--- /dev/null
+++ b/httemplate/browse/part_pkg_taxclass.html
@@ -0,0 +1,27 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Tax Classes',
+ 'name_singular' => 'tax class',
+ 'menubar' => [ 'Add a new tax class' =>
+ $p.'edit/part_pkg_taxclass.html',
+ ],
+ 'query' => { 'table' => 'part_pkg_taxclass', },
+ 'count_query' => 'SELECT COUNT(*) FROM part_pkg_taxclass',
+ 'header' => [ '#', 'Device type' ],
+ 'fields' => [ 'taxclassnum',
+ 'taxclass',
+ ],
+ 'links' => [ $link,
+ $link,
+ ],
+ 'disableable' => 1,
+ 'disabled_statuspos' => 1,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ "${p}edit/part_pkg_taxclass.html?", 'taxclassnum' ];
+
+</%init>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index f1b2836..94afdef 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -55,6 +55,8 @@ function part_export_areyousure(href) {
<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>
+
<TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Modifier</TH>
</TR>
@@ -65,8 +67,15 @@ function part_export_areyousure(href) {
% my @dfields = $svc_x->fields;
% push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
% my @fields =
-% grep { $svc_x->pvf($_)
-% or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
+% grep { my $col = $part_svc->part_svc_column($_);
+% my $def = FS::part_svc->svc_table_fields($svcdb)->{$_};
+% $svc_x->pvf($_)
+% or $_ ne 'svcnum' && (
+% $col->columnflag || ( $col->columnlabel !~ /^\S*$/
+% && $col->columnlabel ne $def->{'label'}
+% )
+% )
+% }
% @dfields ;
% my $rowspan = scalar(@fields) || 1;
% my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart;
@@ -128,21 +137,25 @@ function part_export_areyousure(href) {
</TD>
% unless ( @fields ) {
-% for ( 1..3 ) {
+% for ( 1..4 ) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
% }
% }
%
% my($n1)='';
% foreach my $field ( @fields ) {
-% my $formatter =
-% FS::part_svc->svc_table_fields($svcdb)->{$field}->{format}
-% || sub { shift };
-% my $flag = $part_svc->part_svc_column($field)->columnflag;
%
+% #a few lines of false laziness w/edit/part_svc.cgi
+% my $def = FS::part_svc->svc_table_fields($svcdb)->{$field};
+% 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 $flag = $part_svc_column->columnflag;
<% $n1 %>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $label %></TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html
index 848c58a..a06e5cf 100644
--- a/httemplate/browse/payment_gateway.html
+++ b/httemplate/browse/payment_gateway.html
@@ -10,17 +10,21 @@
},
'count_query' => $count_query,
'header' => [ '#',
+ 'Type',
'Gateway',
'Username',
'Password',
'Action',
+ 'URL',
'Options',
],
'fields' => [ 'gatewaynum',
+ 'namespace_description',
$gateway_sub,
'gateway_username',
sub { ' - '; },
'gateway_action',
+ 'gateway_callback_url',
$options_sub,
],
)
diff --git a/httemplate/browse/pkg_category.html b/httemplate/browse/pkg_category.html
index 20bf1a8..e85c0dd 100644
--- a/httemplate/browse/pkg_category.html
+++ b/httemplate/browse/pkg_category.html
@@ -9,9 +9,9 @@
'extra_sql' => 'ORDER BY categorynum',
},
'count_query' => $count_query,
- 'header' => [ '#', 'Category' ],
- 'fields' => [ 'categorynum', 'categoryname' ],
- 'links' => [ $link, $link ],
+ 'header' => [ '#', 'Category', 'Weight' ],
+ 'fields' => [ 'categorynum', 'categoryname', 'weight' ],
+ 'links' => [ $link, $link, $link ],
)
%>
diff --git a/httemplate/browse/rate_region.html b/httemplate/browse/rate_region.html
index b454a9e..b7d9589 100644
--- a/httemplate/browse/rate_region.html
+++ b/httemplate/browse/rate_region.html
@@ -12,9 +12,11 @@
'order_by' => 'ORDER BY LOWER(regionname)',
},
'count_query' => $count_query,
- 'header' => [ '#', 'Region', 'Country code', 'Prefixes' ],
- 'fields' => [ 'regionnum', 'regionname', 'ccode', 'prefixes' ],
- 'links' => [ $link, $link, $link, $link ],
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => \@align,
+ 'xls_format' => \@xls_format,
)
%>
<%once>
@@ -39,12 +41,12 @@ if ( driver_name =~ /^Pg/ ) {
" ELSE npa || '-' || nxx ".
" END";
my $prefixes_sql = "SELECT $prefix_sql $fromwhere AND npa IS NOT NULL";
- $select .= "( SELECT countrycode $fromwhere LIMIT 1 ) AS ccode,
- ARRAY_TO_STRING( ARRAY($prefixes_sql), ',' ) AS prefixes";
+ $select .= "( SELECT '+'||countrycode $fromwhere LIMIT 1 ) AS ccode,
+ ARRAY_TO_STRING( ARRAY($prefixes_sql), ', ' ) AS prefixes";
} elsif ( driver_name =~ /^mysql/i ) {
$join = 'LEFT JOIN rate_prefix USING ( regionnum )';
- $select .= "GROUP_CONCAT( DISTINCT countrycode ) AS ccode,
- GROUP_CONCAT( npa ORDER BY npa ) AS prefixes ";
+ $select .= "'+'||GROUP_CONCAT( DISTINCT countrycode ) AS ccode,
+ GROUP_CONCAT( npa ORDER BY npa SEPARATOR ', ' ) AS prefixes ";
$group_sql = 'GROUP BY regionnum, regionname';
} else {
die 'unknown database '. driver_name;
@@ -52,12 +54,20 @@ if ( driver_name =~ /^Pg/ ) {
my $base_count_sql = 'SELECT COUNT(*) FROM rate_region';
+tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
+
</%once>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+my @header = ( '#', 'Region', 'Country code', 'Prefixes' );
+my @fields = ( 'regionnum', 'regionname', 'ccode', 'prefixes' );
+my @links = ( ($link) x 4 );
+my @align = ( 'right', 'left', 'right', 'left' );
+my @xls_format = ( ({ locked=>1, bg_color=>22 }) x 4 );
+
$cgi->param('dummy', 1);
my $countrycode_filter_change =
"window.location = '".
@@ -76,6 +86,41 @@ if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) {
$count_query .= " WHERE $ccode_sql = '$1'";
}
+sub _rate_detail_factory {
+ my( $rate, $field ) = @_;
+ return sub {
+ my $rate_detail = $rate->dest_detail(shift)
+ || new FS::rate_region { 'min_included' => 0,
+ 'min_charge' => 0,
+ 'sec_granularity' => 0,
+ };
+ my $value = $rate_detail->$field();
+ $field eq 'sec_granularity' ? $granularity{$value} : $value;
+ };
+}
+
+if ( $cgi->param('show_rates') ) {
+ foreach my $rate ( qsearch('rate', {}) ) {
+
+ my $label = $rate->ratenum.': '. $rate->ratename;
+ push @header, "$label: Included minutes/calls",
+ "$label: Charge per minute/call",
+ "$label: Granularity",
+ "$label: Usage class";
+
+ #closure me harder
+ push @fields, _rate_detail_factory($rate, 'min_included'),
+ _rate_detail_factory($rate, 'min_charge'),
+ _rate_detail_factory($rate, 'sec_granularity'),
+ _rate_detail_factory($rate, 'classnum');
+
+ push @links, ( ('') x 4 );
+ push @xls_format, ( ({}) x 4 );
+
+ }
+
+}
+
my $html_posttotal =
'(show country code: '.
qq(<SELECT NAME="countrycode" onChange="$countrycode_filter_change">).
diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi
index c6e615d..501d362 100755
--- a/httemplate/browse/svc_acct_pop.cgi
+++ b/httemplate/browse/svc_acct_pop.cgi
@@ -23,6 +23,7 @@
$num_accounts_sub,
],
'align' => 'rllrrrr',
+ 'links' => [ map { $svc_acct_pop_link } (1..6) ],
)
%>
<%init>
diff --git a/httemplate/config/config-delete.cgi b/httemplate/config/config-delete.cgi
index cdac434..a05cb1e 100644
--- a/httemplate/config/config-delete.cgi
+++ b/httemplate/config/config-delete.cgi
@@ -2,14 +2,21 @@
die "access denied\n"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-die "No configuration item specified (bad URL)!" unless $cgi->keywords;
-my ($query) = $cgi->keywords;
-$query =~ /^(\d+)$/;
+$cgi->param('confnum') =~ /^(\d+)$/ or die "illegal or missing confnum";
my $confnum = $1;
my $conf = qsearchs('conf', {'confnum' => $confnum});
die "Configuration not found!" unless $conf;
$conf->delete;
+my $redirect = popurl(2);
+if ( $cgi->param('redirect') eq 'config_view_showagent' ) {
+ $redirect .= 'config/config-view.cgi?showagent=1#'. $conf->name;
+} elsif ( $cgi->param('redirect') eq 'config_view' ) {
+ $redirect .= 'config/config-view.cgi';
+} else {
+ $redirect .= 'browse/agent.cgi';
+}
+
</%init>
-<% $cgi->redirect(popurl(2) . "browse/agent.cgi") %>
+<% $cgi->redirect($redirect) %>
diff --git a/httemplate/config/config-image.cgi b/httemplate/config/config-image.cgi
index 892f7c6..0de9d42 100644
--- a/httemplate/config/config-image.cgi
+++ b/httemplate/config/config-image.cgi
@@ -1,4 +1,4 @@
-<% $conf->config_binary($name, $agentnum) %>
+<% $logo %>
<%init>
die "access denied"
@@ -16,4 +16,7 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$agentnum = $1;
}
+my $logo = $conf->config_binary($name, $agentnum);
+$logo = eps2png($logo) if $name =~ /\.eps$/i;
+
</%init>
diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi
index 84bfdef..a241de8 100644
--- a/httemplate/config/config-process.cgi
+++ b/httemplate/config/config-process.cgi
@@ -41,15 +41,18 @@ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
} else {
push @delete, $i->key;
}
- } elsif ( $type eq 'text' || $type eq 'select' || $type eq 'select-sub' ) {
- if ( $cgi->param($i->key.$n) ne '' ) {
- $conf->set($i->key, $cgi->param($i->key.$n), $agentnum);
+ } elsif (
+ $type =~ /^(editlist|selectmultiple)$/
+ or ( $type =~ /^select(-(sub|part_svc|part_pkg))?$/ || $i->multiple )
+ ) {
+ if ( scalar(@{[ $cgi->param($i->key.$n) ]}) ) {
+ $conf->set($i->key, join("\n", @{[ $cgi->param($i->key.$n) ]} ), $agentnum);
} else {
$conf->delete($i->key, $agentnum);
}
- } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' ) {
- if ( scalar(@{[ $cgi->param($i->key.$n) ]}) ) {
- $conf->set($i->key, join("\n", @{[ $cgi->param($i->key.$n) ]} ), $agentnum);
+ } elsif ( $type =~ /^(text|select(-(sub|part_svc|part_pkg))?)$/ ) {
+ if ( $cgi->param($i->key.$n) ne '' ) {
+ $conf->set($i->key, $cgi->param($i->key.$n), $agentnum);
} else {
$conf->delete($i->key, $agentnum);
}
@@ -65,7 +68,10 @@ $conf->delete($_, $agentnum) foreach @delete;
<SCRIPT TYPE="text/javascript">
% my $n = 0;
% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
- var configCell = window.top.document.getElementById('<% $i->key. $n %>');
+ var configCell = window.top.document.getElementById('<% $agentnum. $i->key. $n %>');
+ if ( ! configCell ) {
+ window.top.location.reload();
+ }
//alert('found cell ' + configCell);
% if ( $type eq 'textarea'
% || $type eq 'editlist'
@@ -87,8 +93,22 @@ $conf->delete($_, $agentnum) foreach @delete;
configCell.style.backgroundColor = '#ff0000';
configCell.innerHTML = 'NO';
% }
+% } elsif ( $type eq 'select' && $i->select_hash ) {
+% my %hash;
+% if ( ref($i->select_hash) eq 'ARRAY' ) {
+% tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
+% } else {
+% tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
+% }
+ configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' |js_string %>;
+
% } elsif ( $type eq 'text' || $type eq 'select' ) {
configCell.innerHTML = <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' |js_string %>;
+% } elsif ( $type =~ /^select-(part_svc|part_pkg)$/ && ! $i->multiple ) {
+ configCell.innerHTML =
+ <% $conf->config($i->key, $agentnum) |js_string %>
+%# + ': ' +
+%# <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>;
% } elsif ( $type eq 'select-sub' ) {
configCell.innerHTML =
<% $conf->config($i->key, $agentnum) |js_string %> + ': ' +
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
index 0f5fd62..51535d7 100644
--- a/httemplate/config/config-view.cgi
+++ b/httemplate/config/config-view.cgi
@@ -3,6 +3,21 @@
Click on a configuration value to change it.
<BR><BR>
+% unless ( $page_agent ) {
+%
+% if ( $cgi->param('showagent') ) {
+% $cgi->param('showagent', 0);
+ ( <a href="<% $cgi->self_url %>">hide agent overrides</a> )
+% $cgi->param('showagent', 1);
+% } else {
+% $cgi->param('showagent', 1);
+ ( <a href="<% $cgi->self_url %>">show agent overrides</a> )
+% $cgi->param('showagent', 0);
+% }
+%
+% }
+<BR><BR>
+
<% include('/elements/init_overlib.html') %>
% if ($FS::UID::use_confcompat) {
@@ -33,7 +48,8 @@ Click on a configuration value to change it.
</tr>
% foreach my $i (@{ $section_items{$section} }) {
% my @types = ref($i->type) ? @{$i->type} : ($i->type);
-% my( $width, $height ) = ( 522, 336 );
+%# my( $width, $height ) = ( 522, 336 );
+% my( $width, $height ) = ( 600, 336 );
% if ( grep $_ eq 'textarea', @types ) {
% #800x600
% $width = 763;
@@ -42,6 +58,34 @@ Click on a configuration value to change it.
% #$width =
% #$height =
% }
+%
+% my @agents = ();
+% my @add_agents = ();
+% if ( $page_agent ) {
+% @agents = ( $page_agent );
+% } else {
+% @agents = ( '' );
+% if ( $i->per_agent ) {
+% foreach my $agent (@all_agents) {
+% if ( defined($conf->conf( $i->key, $agent->agentnum, 1 ) ) ) {
+% push @agents, $agent;
+% } else {
+% push @add_agents, $agent;
+% }
+% }
+% }
+% }
+%
+% foreach my $agent ( @agents ) {
+% my $agentnum = $agent ? $agent->agentnum : '';
+%
+% next if $section eq 'deprecated' && ! $conf->exists($i->key, $agentnum);
+%
+% my $label = $i->key;
+% $label = '['. $agent->agent. "] $label"
+% if $agent && $cgi->param('showagent');
+%
+% #indentation :/
<tr>
<td><% include('/elements/popup_link.html',
@@ -50,10 +94,24 @@ Click on a configuration value to change it.
'width' => $width,
'height' => $height,
'actionlabel' => 'Enter configuration value',
- 'label' => '<b>'. $i->key. '</b>',
- 'aname' => $i->key,
+ 'label' => "<b>$label</b>",
+ 'aname' => $i->key, #agentnum
+ # if $cgi->param('showagent')?
)
%>: <% $i->description %>
+% if ( $agent && $cgi->param('showagent') ) {
+% my $confnum = $conf->conf( $i->key, $agent->agentnum, 1 )->confnum;
+ (<A HREF="javascript:areyousure('delete this agent override', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view_showagent')">delete agent override</A>)
+% } elsif ( $i->base_key
+% || ( $deleteable{$i->key} && $conf->exists($i->key) ) ) {
+% my $confnum =
+% $agent
+% ? $conf->conf( $i->key, $agent->agentnum, 1 )->confnum
+% : $conf->conf( $i->key )->confnum;
+% my $showagent = $cgi->param('showagent') ? '_showagent' : '';
+ (<A HREF="javascript:areyousure('delete this configuration item', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view<%$showagent%>')">delete configuration item</A>)
+% }
+
</td>
<td><table border=0>
@@ -66,77 +124,181 @@ Click on a configuration value to change it.
<td><font color="#ff0000">no type</font></td>
</tr>
-% } elsif ( $type eq 'image' ) {
+% } elsif ( $type eq 'image' ) {
<tr>
-
- <% $conf->exists($i->key, $agentnum)
- ? '<img src="config-image.cgi?key='. $i->key.
- ';agentnum='. $agentnum. '">'
- : 'empty'
- %>
+ <td bgcolor='#ffffff'>
+ <% $conf->exists($i->key, $agentnum)
+ ? '<img src="config-image.cgi?key='. $i->key.
+ ';agentnum='. $agentnum. '">'
+ : 'empty'
+ %>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <% $conf->exists($i->key, $agentnum)
+ ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
+ : ''
+ %>
+ </td>
</tr>
-% } elsif ( $type eq 'binary' ) {
+% } elsif ( $type eq 'binary' ) {
<tr>
-
- <% $conf->exists($i->key, $agentnum)
- ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
- : 'empty'
- %>
+ <td>
+ <% $conf->exists($i->key, $agentnum)
+ ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
+ : 'empty'
+ %>
+ </td>
</tr>
% } elsif ( $type eq 'textarea'
% || $type eq 'editlist'
-% || $type eq 'selectmultiple' ) {
+% || $type eq 'selectmultiple'
+% )
+% {
<tr>
- <td id="<% $i->key.$n %>" bgcolor="#ffffff">
-<font size="-2"><pre>
-<% encode_entities(join("\n",
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+<font size="-2"><pre><% encode_entities(join("\n",
map { length($_) > 88 ? substr($_,0,88).'...' : $_ }
$conf->config($i->key, $agentnum)
) )
-%>
-</pre></font>
+%></pre></font>
</td>
</tr>
+
% } elsif ( $type eq 'checkbox' ) {
<tr>
- <td id="<% $i->key.$n %>" bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
</tr>
+
+% } elsif ( $type eq 'select' && $i->select_hash ) {
+%
+% my %hash;
+% if ( ref($i->select_hash) eq 'ARRAY' ) {
+% tie %hash, 'Tie::IxHash', '' => '', @{ $i->select_hash };
+% } else {
+% tie %hash, 'Tie::IxHash', '' => '', %{ $i->select_hash };
+% }
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% $conf->exists($i->key, $agentnum) ? $hash{ $conf->config($i->key, $agentnum) } : '' %>
+ </td>
+ </tr>
+
% } elsif ( $type eq 'text' || $type eq 'select' ) {
<tr>
- <td id="<% $i->key.$n %>" bgcolor="#ffffff">
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
<% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' %>
- </td></tr>
-% } elsif ( $type eq 'select-sub' ) {
+ </td>
+ </tr>
+
+% } elsif ( $type eq 'select-sub' ) {
<tr>
- <td id="<% $i->key.$n %>" bgcolor="#ffffff">
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
<% $conf->config($i->key, $agentnum) %>:
<% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) %>
</td>
</tr>
-% } else {
+
+% } elsif ( $type =~ /^select-(part_svc|part_pkg)$/ ) {
+% my @keys = $conf->config($i->key, $agentnum);
+
+ <tr>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#ffffff">
+ <% join('<BR>', map { $_ # ': '. $svc, $pkg, whatever
+ }
+ @keys
+ )
+ %>
+ </td>
+ </tr>
+
+% } else {
<tr><td>
<font color="#ff0000">unknown type <% $type %></font>
</td></tr>
-% }
+% }
% $n++;
% }
</table></td>
</tr>
-% }
+
+% } # foreach my $agentnum
+
+% if ( @add_agents ) {
+
+ <tr>
+ <td>
+ <FORM>
+ Add <b><% $i->key %></b> override for
+ <% include('/elements/select-agent.html',
+ 'agents' => \@add_agents,
+ 'empty_label' => 'Select agent',
+ 'onchange' => "agent_changed",
+ 'id' => 'agent_'. $i->key,
+ )
+ %>
+ agent
+
+% my $agent_el = "document.getElementById('agent_". $i->key. "')";
+ <INPUT TYPE = "button"
+ VALUE = "Add"
+ ID = "add_<% $i->key %>"
+ DISABLED
+ onClick = "<%
+ include('/elements/popup_link_onclick.html',
+ 'action' =>
+ 'config.cgi?key='. $i->key.
+ ";agentnum=' + ".
+ "$agent_el.options[$agent_el.selectedIndex].value".
+ " + '",
+ 'width' => $width,
+ 'height' => $height,
+ 'actionlabel' => 'Enter configuration value',
+ )
+ %>"
+ >
+ </FORM>
+ </td>
+ </tr>
+
+% } #if @add_agents
+
+% } # foreach my $i
</table><br><br>
-% }
+% } # foreach my $nav_section
+
+<SCRIPT TYPE="text/javascript">
+
+ function agent_changed(what) {
+ var key = what.id.substring(6); // trim agent_
+ var button = document.getElementById('add_'+key);
+ if ( what.selectedIndex > 0 ) {
+ button.disabled = false;
+ } else {
+ button.disabled = true;
+ }
+ }
+
+ function areyousure(what, href) {
+ if ( confirm("Are you sure you want to " + what + "?") == true )
+ window.location.href = href;
+ }
+
+</SCRIPT>
</body></html>
<%init>
@@ -144,26 +306,29 @@ Click on a configuration value to change it.
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $agentnum = '';
+my $page_agent = '';
my $title;
my @menubar = ();
if ($cgi->param('agentnum') =~ /^(\d+)$/) {
- $agentnum = $1;
- my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
- die "Agent $agentnum not found!" unless $agent;
+ my $page_agentnum = $1;
+ $page_agent = qsearchs('agent', { 'agentnum' => $page_agentnum } );
+ die "Agent $page_agentnum not found!" unless $page_agent;
push @menubar, 'View all agents' => $p.'browse/agent.cgi';
- $title = 'Agent Configuration for '. $agent->agent;
+ $title = 'Agent Configuration for '. $page_agent->agent;
} else {
$title = 'Global Configuration';
}
my $conf = new FS::Conf;
-my @config_items = grep { $agentnum ? $_->per_agent : 1 }
- grep { $_->key != ~/^invoice_(html|latex|template)/ }
+my @config_items = grep { $page_agent ? $_->per_agent : 1 }
+ grep { $page_agent ? 1 : !$_->agentonly }
$conf->config_items;
+my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress );
+my %deleteable = map { $_ => 1 } @deleteable;
+
my @sections = qw(required billing username password UI session shell BIND );
push @sections, '', 'deprecated';
@@ -174,4 +339,10 @@ foreach my $section (@sections) {
@sections = grep scalar( @{ $section_items{$_} } ), @sections;
+my @all_agents = ();
+if ( $cgi->param('showagent') ) {
+ @all_agents = qsearch('agent', { 'disabled' => '' } );
+}
+warn 'all agents: '. join('-', @all_agents);
+
</%init>
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
index f390c64..45d77ff 100644
--- a/httemplate/config/config.cgi
+++ b/httemplate/config/config.cgi
@@ -84,11 +84,9 @@ Setting <b><% $key %></b>
% '' => '', map { $_ => $_ } @{ $config_item->select_enum };
% } elsif ( $config_item->select_hash ) {
% if ( ref($config_item->select_hash) eq 'ARRAY' ) {
-% tie %hash, 'Tie::IxHash',
-% '' => '', @{ $config_item->select_hash };
+% tie %hash, 'Tie::IxHash', '' => '', @{ $config_item->select_hash };
% } else {
-% tie %hash, 'Tie::IxHash',
-% '' => '', %{ $config_item->select_hash };
+% tie %hash, 'Tie::IxHash', '' => '', %{ $config_item->select_hash };
% }
% } else {
% %hash = ( '' => 'WARNING: neither select_enum nor select_hash specified in Conf.pm for configuration option "'. $key. '"' );
@@ -269,9 +267,24 @@ Setting <b><% $key %></b>
<td><input type="button" value="add" onClick="doadd<% "$key$n" %>(this.form)"></td>
</tr></table>
+% } elsif ( $element_types{$type} ) {
+%
+% my %opt = ( 'element_name' => "$key$n",
+% 'empty_label' => ' ',
+% );
+% if ( $config_item->multiple ) {
+% $opt{'multiple'} = 1 if $config_item->multiple;
+% $opt{'curr_value'} = [ $conf->config($key, $agentnum) ];
+% } else {
+% $opt{'curr_value'} =
+% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '';
+% }
+
+ <% include("/elements/$type.html", %opt ) %>
+
% } else {
- <font color="#ff0000">unknown type <% $type %></font>
+ <font color="#ff0000">unknown type <% $type %></font>
% }
% $n++;
@@ -291,10 +304,13 @@ Setting <b><% $key %></b>
<%once>
my $conf = new FS::Conf;
-my @config_items = grep { $_->key != ~/^invoice_(html|latex|template)/ }
- $conf->config_items;
+my @config_items = $conf->config_items;
my %confitems = map { $_->key => $_ } @config_items;
+my %element_types = map { $_ => 1 } qw(
+ select-part_svc select-part_pkg
+);
+
</%once>
<%init>
diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html
index dee4247..04af73d 100644
--- a/httemplate/docs/about.html
+++ b/httemplate/docs/about.html
@@ -1,17 +1,17 @@
-<% include('/elements/header-popup.html', 'Freeside') %>
+<% include('/elements/header-popup.html', { title=>'Freeside', nobr=>1 } ) %>
<% include('/elements/init_overlib.html') %>
<CENTER>
-<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER=0"><BR>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
<H3>version <% $FS::VERSION %></H3>
</CENTER>
<CENTER>
-<FONT SIZE="-1">&copy; 2008 Freeside Internet Services, Inc.<BR>
+<FONT SIZE="-1">&copy; 2009 Freeside Internet Services, Inc.<BR>
All rights reserved.<BR>
Licensed under the terms of the<BR>
-GNU <i>Affero</i> General Public License.<BR>
+GNU <b>Affero</b> General Public License.<BR>
</FONT>
</CENTER>
<BR>
diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html
index 3c5564d..d927722 100644
--- a/httemplate/docs/credits.html
+++ b/httemplate/docs/credits.html
@@ -24,17 +24,16 @@
<CENTER>Freeside</CENTER>
</FONT>
-<BR>
-
<CENTER>
-<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER=0"><BR>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
<H3>version <% $FS::VERSION %></H3>
</CENTER>
<CENTER>
-<H3>Core team</H3>
+<H3>Core Team</H3>
Peter Bowen<BR>
+Jeremy Davis<BR>
Jeff Finucane<BR>
Jason Hall<BR>
Kristian Hoffman<BR>
@@ -42,6 +41,11 @@ Ivan Kohler<BR>
Richard Siddall<BR>
<BR>
+<H3>Core Emeritus</H3>
+Brian McCane<BR>
+Matt Simerson<BR>
+<BR>
+
<H3>Contributors</H3>
Stephen Amadei<BR>
Eric Arvidsson<BR>
@@ -74,19 +78,18 @@ Greg Kuhnert<BR>
Randall Lucas<BR>
Foteos Macrides<BR>
Roger Mangraviti<BR>
-Brian McCane<BR>
mimooh<BR>
Mack Nagashima<BR>
Matt Peterson<BR>
Luke Pfeifer<BR>
Ricardo Signes<BR>
-Matt Simerson<BR>
Steve Simitzis<BR>
Jason Spence<BR>
James Switzer<BR>
Audrey Tang<BR>
Jason Thomas<BR>
Jesse Vincent<BR>
+Johan Vromans<BR>
Mark Wells<BR>
Peter Wemm<BR>
Mark Williamson<BR>
@@ -147,7 +150,7 @@ function myHeight() {
return document.body.document.height;
else
*/
- return 1700; // approx height (add more per contributors)
+ return 1850; // approx height (add more per contributors)
}
document.body.style.overflow = 'hidden';
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html
index 5453730..fc3da69 100644
--- a/httemplate/docs/license.html
+++ b/httemplate/docs/license.html
@@ -1,12 +1,12 @@
-<% include('/elements/header-popup.html', 'Freeside') %>
+<% include('/elements/header-popup.html', { title=>'Freeside', nobr=>1 } ) %>
<CENTER>
-<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER=0"><BR>
+<IMG SRC="<%$fsurl%>images/small-logo.png" BORDER="0"><BR>
<H3>version <% $FS::VERSION %></H3>
</CENTER>
<P>
-Copyright &copy; 2005-2008 Freeside Internet Services, Inc.<BR>
+Copyright &copy; 2005-2009 Freeside Internet Services, Inc.<BR>
Copyright &copy; 2000-2005 Ivan Kohler<BR>
Copyright &copy; 1999 Silicon Interactive Software Design<BR>
All rights reserved<BR>
@@ -100,6 +100,10 @@ terms of the BSD license.<BR>
&copy; 2005 modernmethod, inc<BR>
Perl backend version &copy; 2005 Nathan Schmidt
+<P>
+Contains code derived from eps2png by Johan Vromans, licensed under the same
+terms as Perl (GPL/Artistic).
+
<!-- artwork -->
<P>
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
index b2c89c3..5752c8d 100755
--- a/httemplate/edit/REAL_cust_pkg.cgi
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -32,6 +32,11 @@
</TR>
<TR>
+ <TD ALIGN="right">Custom</TD>
+ <TD BGCOLOR="#ffffff"><% $part_pkg->custom %></TD>
+ </TR>
+
+ <TR>
<TD ALIGN="right">Comment</TD>
<TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD>
</TR>
@@ -41,6 +46,7 @@
<TD BGCOLOR="#ffffff"><% $cust_pkg->otaker %></TD>
</TR>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &>
<& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &>
<& .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 &>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
index 215542d..a0af9fa 100755
--- a/httemplate/edit/agent.cgi
+++ b/httemplate/edit/agent.cgi
@@ -46,13 +46,21 @@ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
<TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $agent->disabled eq 'Y' ? ' CHECKED' : '' %>></TD>
</TR>
+% if ( $conf->exists('agent-invoice_template') ) {
+
<% include('/elements/tr-select-invoice_template.html',
'label' => 'Invoice template',
'field' => 'invoice_template',
'curr_value' => $agent->invoice_template,
)
%>
-
+
+% } else {
+
+ <INPUT TYPE="hidden" NAME="invoice_template" VALUE="<% $agent->invoice_template %>">
+
+% }
+
% if ( $conf->config('ticket_system') ) {
% my $default_queueid = $conf->config('ticket_system-default_queueid');
% my $default_queue = FS::TicketSystem->queue($default_queueid);
diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi
index abf4bf8..8a6fbc2 100755
--- a/httemplate/edit/agent_type.cgi
+++ b/httemplate/edit/agent_type.cgi
@@ -20,7 +20,7 @@ Select which packages agents of this type may sell to customers<BR>
'source_obj' => $agent_type,
'link_table' => 'type_pkgs',
'target_table' => 'part_pkg',
- 'name_callback' => sub { $_[0]->pkg. ' - '. $_[0]->comment; },
+ 'name_callback' => sub { $_[0]->pkg_comment(nopkgpart => 1); },
'target_link' => $p.'edit/part_pkg.cgi?',
'disable-able' => 1,
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
index c9ca31f..5785a05 100755
--- a/httemplate/edit/cust_credit.cgi
+++ b/httemplate/edit/cust_credit.cgi
@@ -40,6 +40,16 @@ Credit
<TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>
</TR>
+% if ( $conf->exists('pkg-balances') ) {
+ <% include('/elements/tr-select-cust_pkg-balances.html',
+ 'custnum' => $custnum,
+ 'cgi' => $cgi
+ )
+ %>
+% } else {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% }
+
</TABLE>
<BR>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index d3004f1..15c9f45 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -1,14 +1,18 @@
<% include('/elements/header.html',
"Customer $action",
'',
- ' onUnload="myclose()"'
+ ' onUnload="myclose()"' #hmm, in billing.html
) %>
-<% include('/elements/init_overlib.html') %>
-
<% include('/elements/error.html') %>
-<FORM NAME="topform" STYLE="margin-bottom: 0">
+<FORM NAME = "CustomerForm"
+ METHOD = "POST"
+ ACTION = "<% popurl(1) %>process/cust_main.cgi"
+%# STYLE = "margin-bottom: 0"
+%# STYLE="margin-top: 0; margin-bottom: 0">
+>
+
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
% if ( $custnum ) {
@@ -19,101 +23,25 @@
<BR><BR>
% }
-<% &ntable("#cccccc") %>
-
-%# agent
-<% include('/elements/tr-select-agent.html',
- 'curr_value' => $cust_main->agentnum,
- 'label' => "<B>${r}Agent</B>",
- 'empty_label' => 'Select agent',
- 'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
- )
-%>
-
-%# agent_custid
-% if ( $conf->exists('cust_main-edit_agent_custid') ) {
-
- <TR>
- <TD ALIGN="right">Customer identifier</TD>
- <TD><INPUT TYPE="text" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>"></TD>
- </TR>
-
-% } else {
-
- <INPUT TYPE="hidden" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>">
-
-% }
-
-%# referral (advertising source)
-%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0;
-%if ( $custnum && ! $conf->exists('editreferrals') ) {
-
- <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>">
-
-% } else {
-
- <% include('/elements/tr-select-part_referral.html',
- 'curr_value' => $refnum
- )
- %>
-% }
-
-
-%# referring customer
-%my $referring_cust_main = '';
-%if ( $cust_main->referral_custnum
-% and $referring_cust_main =
-% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
-%) {
-
- <TR>
- <TD ALIGN="right">Referring customer</TD>
- <TD>
- <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A>
- </TD>
- </TR>
- <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>">
-% } elsif ( ! $conf->exists('disable_customer_referrals') ) {
-
-
- <TR>
- <TD ALIGN="right">Referring customer</TD>
- <TD>
- <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> -->
- <% include('/elements/search-cust_main.html',
- 'field_name' => 'referral_custnum',
- )
- %>
- </TD>
- </TR>
-% } else {
-
-
- <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">
-% }
-
-
-</TABLE>
-
-<!-- birthdate -->
+%# agent, agent_custid, refnum (advertising source), referral_custnum
+<% include('cust_main/top_misc.html', $cust_main, 'custnum' => $custnum ) %>
+%# birthdate
% if ( $conf->exists('cust_main-enable_birthdate') ) {
-
<BR>
- <% ntable("#cccccc", 2) %>
- <% include ('/elements/tr-input-date-field.html',
- 'birthdate',
- $cust_main->birthdate,
- 'Date of Birth',
- $conf->config('date_format') || "%m/%d/%Y",
- 1)
- %>
-
- </TABLE>
+ <% include('cust_main/birthdate.html', $cust_main) %>
+% }
+%# latitude and longitude
+% if ( $conf->exists('cust_main-require_censustract') ) {
+% my ($latitude, $longitude) = $cust_main->service_coordinates;
+% $latitude ||= $conf->config('company_latitude') || '';
+% $longitude ||= $conf->config('company_longitude') || '';
+ <INPUT NAME="latitude" TYPE="hidden" VALUE="<% $latitude |h %>">
+ <INPUT NAME="longitude" TYPE="hidden" VALUE="<% $longitude |h %>">
% }
-<!-- contact info -->
+%# contact info
% my $same_checked = '';
% my $ship_disabled = '';
@@ -128,8 +56,9 @@
% }
% }
-<BR><BR>
-Billing address
+<BR>
+<FONT SIZE="+1"><B>Billing address</B></FONT>
+
<% include('cust_main/contact.html',
'cust_main' => $cust_main,
'pre' => '',
@@ -199,7 +128,8 @@ function samechanged(what) {
</SCRIPT>
<BR>
-Service address
+<FONT SIZE="+1"><B>Service address</B></FONT>
+
(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>>same as billing address)
<% include('cust_main/contact.html',
'cust_main' => $cust_main,
@@ -209,528 +139,133 @@ Service address
)
%>
-
-<!-- billing info -->
-
+%# billing info
<% include( 'cust_main/billing.html', $cust_main,
'payinfo' => $payinfo,
'invoicing_list' => \@invoicing_list,
)
%>
-<% include( '/elements/xmlhttp.html',
- 'url' => $p.'misc/xmlhttp-cust_main-address_standardize.html',
- 'subs' => [ 'address_standardize' ],
- #'method' => 'POST', #could get too long?
- )
-%>
-
-<SCRIPT>
-function bottomfixup(what) {
-
- //i don't think we need to copy things between two forms anymore, modern
- //browsers are fine with DIVs inside FORMs
-
- var topvars = new Array(
- 'birthdate',
-
- 'custnum', 'agentnum', 'agent_custid', 'refnum', 'referral_custnum',
-
- 'last', 'first', 'ss', 'company',
- 'address1', 'address2', 'city',
- 'county', 'state', 'zip', 'country',
- 'daytime', 'night', 'fax',
- 'stateid', 'stateid_state',
-
- 'same',
-
- 'ship_last', 'ship_first', 'ship_company',
- 'ship_address1', 'ship_address2', 'ship_city',
- 'ship_county', 'ship_state', 'ship_zip', 'ship_country',
- 'ship_daytime','ship_night', 'ship_fax',
-
- 'geocode',
-
- 'select' // XXX key
- );
-
- var layervars = new Array(
- 'payauto',
- 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
- 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
- 'paystart_month', 'paystart_year', 'payissue',
- 'payip',
- 'paid'
- );
-
- var billing_bottomvars = new Array(
- 'tax',
- 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
- 'invoice_terms',
- 'spool_cdr',
- 'squelch_cdr'
- );
-
- for ( f=0; f < topvars.length; f++ ) {
- var field = topvars[f];
- copyelement( document.topform.elements[field],
- document.bottomform.elements[field]
- );
- }
-
- var layerform = document.topform.select.options[document.topform.select.selectedIndex].value;
- for ( f=0; f < layervars.length; f++ ) {
- var field = layervars[f];
- copyelement( document.forms[layerform].elements[field],
- document.bottomform.elements[field]
- );
- }
-
- for ( f=0; f < billing_bottomvars.length; f++ ) {
- var field = billing_bottomvars[f];
- copyelement( document.billing_bottomform.elements[field],
- document.bottomform.elements[field]
- );
- }
-
- //this part does USPS address correction
-
- // XXX should this be first and should we update the form fields that are
- // displayed???
-
- //var state_el = document.bottomform.elements['state'];
-
- //address_standardize(
- var cust_main = new Array(
- 'company', document.bottomform.elements['company'].value,
- 'address1', document.bottomform.elements['address1'].value,
- 'address2', document.bottomform.elements['address2'].value,
- 'city', document.bottomform.elements['city'].value,
- 'state', document.bottomform.elements['state'].value,
- //'state', state_el.options[ state_el.selectedIndex ].value,
- 'zip', document.bottomform.elements['zip'].value,
-
- 'ship_company', document.bottomform.elements['ship_company'].value,
- 'ship_address1', document.bottomform.elements['ship_address1'].value,
- 'ship_address2', document.bottomform.elements['ship_address2'].value,
- 'ship_city', document.bottomform.elements['ship_city'].value,
- 'ship_state', document.bottomform.elements['ship_state'].value,
- //'ship_state', state_el.options[ state_el.selectedIndex ].value,
- 'ship_zip', document.bottomform.elements['ship_zip'].value
- );
-
- address_standardize( cust_main, update_address );
-
-}
-
-var standardize_address;
-
-function update_address(arg) {
-
- var argsHash = eval('(' + arg + ')');
-
- var changed = argsHash['address_standardized'];
- var ship_changed = argsHash['ship_address_standardized'];
- var error = argsHash['error'];
- var ship_error = argsHash['ship_error'];
-
- //yay closures
- standardize_address = function () {
-
- if ( changed ) {
- document.bottomform.elements['company'].value = argsHash['new_company'];
- document.bottomform.elements['address1'].value = argsHash['new_address1'];
- document.bottomform.elements['address2'].value = argsHash['new_address2'];
- document.bottomform.elements['city'].value = argsHash['new_city'];
- document.bottomform.elements['state'].value = argsHash['new_state'];
- //'state', state_el.options[ state_el.selectedIndex ].value,
- document.bottomform.elements['zip'].value = argsHash['new_zip'];
- }
-
- if ( ship_changed ) {
- document.bottomform.elements['ship_company'].value = argsHash['new_ship_company'];
- document.bottomform.elements['ship_address1'].value = argsHash['new_ship_address1'];
- document.bottomform.elements['ship_address2'].value = argsHash['new_ship_address2'];
- document.bottomform.elements['ship_city'].value = argsHash['new_ship_city'];
- document.bottomform.elements['ship_state'].value = argsHash['new_ship_state'];
- //'state', state_el.options[ state_el.selectedIndex ].value,
- document.bottomform.elements['ship_zip'].value = argsHash['new_ship_zip'];
- }
-
- }
-
-% if ( $conf->exists('enable_taxproducts') ) {
-
- if ( <% $taxpre %>error ) {
-
- if ( document.bottomform.elements['country'].value == 'CA' ||
- document.bottomform.elements['country'].value == 'US'
- )
- {
-
- var url = "cust_main/choose_tax_location.html?data_vendor=cch-zip;city="+document.bottomform.elements['city'].value+";state="+document.bottomform.elements['state'].value+";zip="+document.bottomform.elements['zip'].value+";country="+document.bottomform.elements['country'].value+";";
- // popup a chooser
- OLgetAJAX( url, update_geocode, 300 );
-
- } else {
-
- document.bottomform.elements['geocode'].value = 'DEFAULT';
- document.bottomform.submit();
-
- }
+% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
+% if (!$ro_comments || $cust_main->comments) {
- } else
+ <BR>Comments
+ <% &ntable("#cccccc") %>
+ <TR>
+ <TD>
+ <TEXTAREA NAME = "comments"
+ COLS = 80
+ ROWS = 5
+ WRAP = "HARD"
+ <% $ro_comments %>
+ ><% $cust_main->comments %></TEXTAREA>
+ </TD>
+ </TR>
+ </TABLE>
% }
- if ( changed || ship_changed ) {
-
-% if ( $conf->exists('cust_main-auto_standardize_address') ) {
-
- standardize_address();
- document.bottomform.submit();
-
-% } else {
-
- // popup a confirmation popup
-
- var confirm_change =
- '<CENTER><BR><B>Confirm address standardization</B><BR><BR>' +
- '<TABLE>';
-
- if ( changed ) {
-
- confirm_change = confirm_change +
- '<TR><TH>Entered billing address</TH>' +
- '<TH>Standardized billing address</TH></TR>';
- // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- if ( argsHash['company'] || argsHash['new_company'] ) {
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['company'] +
- '</TD><TD>' + argsHash['new_company'] + '</TD></TR>';
- }
-
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['address1'] +
- '</TD><TD>' + argsHash['new_address1'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['address2'] +
- '</TD><TD>' + argsHash['new_address2'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['city'] + ', ' + argsHash['state'] + ' ' + argsHash['zip'] +
- '</TD><TD>' + argsHash['new_city'] + ', ' + argsHash['new_state'] + ' ' + argsHash['new_zip'] + '</TD></TR>' +
- '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- }
-
- if ( ship_changed ) {
-
- confirm_change = confirm_change +
- '<TR><TH>Entered service address</TH>' +
- '<TH>Standardized service address</TH></TR>';
- // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- if ( argsHash['ship_company'] || argsHash['new_ship_company'] ) {
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['ship_company'] +
- '</TD><TD>' + argsHash['new_ship_company'] + '</TD></TR>';
- }
-
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['ship_address1'] +
- '</TD><TD>' + argsHash['new_ship_address1'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['ship_address2'] +
- '</TD><TD>' + argsHash['new_ship_address2'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['ship_city'] + ', ' + argsHash['ship_state'] + ' ' + argsHash['ship_zip'] +
- '</TD><TD>' + argsHash['new_ship_city'] + ', ' + argsHash['new_ship_state'] + ' ' + argsHash['new_ship_zip'] + '</TD></TR>' +
- '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- }
-
- var addresses = 'address';
- var height = 268;
- if ( changed && ship_changed ) {
- addresses = 'addresses';
- height = 396; // #what
- }
-
- confirm_change = confirm_change +
- '<TR><TD>' +
- '<BUTTON TYPE="button" onClick="document.bottomform.submit();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' +
- '</TD><TD>' +
- '<BUTTON TYPE="button" onClick="standardize_address(); document.bottomform.submit();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' +
- '</TD></TR>' +
- '<TR><TD COLSPAN=2 ALIGN="center">' +
- '<BUTTON TYPE="button" onClick="document.bottomform.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
-
- '</TABLE></CENTER>';
-
- overlib( confirm_change, CAPTION, 'Confirm address standardization', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, height, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
-
-% }
-
- } else {
-
- document.bottomform.submit();
+% unless ( $custnum ) {
- }
-
-}
-
-function update_geocode() {
-
- //yay closures
- set_geocode = function (what) {
-
- //alert(what.options[what.selectedIndex].value);
- var argsHash = eval('(' + what.options[what.selectedIndex].value + ')');
- document.bottomform.elements['city'].value = argsHash['city'];
- document.bottomform.elements['state'].value = argsHash['state'];
- document.bottomform.elements['zip'].value = argsHash['zip'];
- document.bottomform.elements['geocode'].value = argsHash['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 );
-
-}
+ <% include('cust_main/first_pkg.html', $cust_main,
+ 'pkgpart_svcpart' => $pkgpart_svcpart,
+ #svc_acct
+ 'username' => $username,
+ 'password' => $password,
+ 'popnum' => $popnum,
+ 'saved_domsvc' => $saved_domsvc,
+ %svc_phone,
+ )
+ %>
-function copyelement(from, to) {
- if ( from == undefined ) {
- to.value = '';
- } else if ( from.type == 'select-one' ) {
- to.value = from.options[from.selectedIndex].value;
- //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
- } else if ( from.type == 'checkbox' ) {
- if ( from.checked ) {
- to.value = from.value;
- } else {
- to.value = '';
- }
- } else {
- if ( from.value == undefined ) {
- to.value = '';
- } else {
- to.value = from.value;
- }
- }
- //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
-}
+% }
-</SCRIPT>
+<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>">
-<FORM ACTION="<% popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+%# cust_main/bottomfixup.js
% foreach my $hidden (
-% 'birthdate',
-%
-% 'custnum', 'agentnum', 'agent_custid', 'refnum', 'referral_custnum',
-% 'last', 'first', 'ss', 'company',
-% 'address1', 'address2', 'city',
-% 'county', 'state', 'zip', 'country',
-% 'daytime', 'night', 'fax',
-% 'stateid', 'stateid_state',
-%
-% 'same',
-%
-% 'ship_last', 'ship_first', 'ship_company',
-% 'ship_address1', 'ship_address2', 'ship_city',
-% 'ship_county', 'ship_state', 'ship_zip', 'ship_country',
-% 'ship_daytime','ship_night', 'ship_fax',
-%
-% 'geocode',
-%
-% 'select', #XXX key
-%
-% 'payauto',
-% 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
-% 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
-% 'paystart_month', 'paystart_year', 'payissue',
-% 'payip',
-% 'paid',
-%
-% 'tax',
-% 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
-% 'invoice_terms',
-% 'spool_cdr',
-% 'squelch_cdr'
-% ) {
-%
-
- <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
-% }
-%
-% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
-% if (!$ro_comments || $cust_main->comments) {
-
-<BR>Comments
-<% &ntable("#cccccc") %>
- <TR>
- <TD>
- <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments" <%$ro_comments%>><% $cust_main->comments %></TEXTAREA>
- </TD>
- </TR>
-</TABLE>
-%
-% }
-%
-%unless ( $custnum ) {
-% # pry the wrong place for this logic. also pretty expensive
-% #use FS::part_pkg;
-%
-% #false laziness, copied from FS::cust_pkg::order
-% my $pkgpart;
-% my $agentnum = '';
-% my @agents = $FS::CurrentUser::CurrentUser->agents;
-% if ( scalar(@agents) == 1 ) {
-% # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
-% $pkgpart = $agents[0]->pkgpart_hashref;
-% $agentnum = $agents[0]->agentnum;
-% } else {
-% #can't know (agent not chosen), so, allow all
-% $agentnum = 'all';
-% my %typenum;
-% foreach my $agent ( @agents ) {
-% next if $typenum{$agent->typenum}++;
-% $pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
-% }
-% }
-% #eslaf
-%
-% my @part_pkg = grep { $_->svcpart('svc_acct')
-% && ( $pkgpart->{ $_->pkgpart }
-% || $agentnum eq 'all'
-% || ( $agentnum ne 'all'
-% && $agentnum
-% && $_->agentnum
-% && $_->agentnum == $agentnum
-% )
-% )
-% }
-% qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
-%
-% if ( @part_pkg ) {
-%
-% # print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"),
-% #apiabuse & undesirable wrapping
-%
-%
-
- <BR>First package
- <% ntable("#cccccc") %>
-
- <TR>
- <TD COLSPAN=2>
- <% include('cust_main/select-domain.html',
- 'pkgparts' => \@part_pkg,
- 'saved_pkgpart' => $saved_pkgpart,
- 'saved_domsvc' => $saved_domsvc,
- )
- %>
- </TD>
- </TR>
-%
-% #false laziness: (mostly) copied from edit/svc_acct.cgi
-% #$ulen = $svc_acct->dbdef_table->column('username')->length;
-% my $ulen = dbdef->table('svc_acct')->column('username')->length;
-% my $ulen2 = $ulen+2;
-% my $passwordmax = $conf->config('passwordmax') || 8;
-% my $pmax2 = $passwordmax + 2;
-%
-
-
- <TR>
- <TD ALIGN="right">Username</TD>
- <TD>
- <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>>
- </TD>
- </TR>
-
- <TR>
- <TD ALIGN="right">Domain</TD>
- <TD>
- <SELECT NAME="domsvc">
- <OPTION>(none)</OPTION>
- </SELECT>
- </TD>
- </TR>
-
- <TR>
- <TD ALIGN="right">Password</TD>
- <TD>
- <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $passwordmax %>>
- (blank to generate)
- </TD>
- </TR>
-
- <TR>
- <TD ALIGN="right">Access number</TD>
- <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
- </TR>
- </TABLE>
-% }
+% 'payauto',
+% 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+% 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+% 'paystart_month', 'paystart_year', 'payissue',
+% 'payip',
+% 'paid',
+% ) {
+ <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
% }
+<% include('cust_main/bottomfixup.html') %>
-<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>">
-<BR>
-<INPUT TYPE="button" NAME="submitButton" ID="submitButton" VALUE="<% $custnum ? "Apply Changes" : "Add Customer" %>" onClick="document.bottomform.submitButton.disabled=true; bottomfixup(this.form);">
<BR>
+<INPUT TYPE = "button"
+ NAME = "submitButton"
+ ID = "submitButton"
+ VALUE = "<% $custnum ? "Apply Changes" : "Add Customer" %>"
+ onClick = "this.disabled=true; bottomfixup(this.form);"
+>
</FORM>
<% include('/elements/footer.html') %>
<%init>
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
-
-#for misplaced logic below
-#use FS::part_pkg;
+my $curuser = $FS::CurrentUser::CurrentUser;
-#for false laziness below (now more properly lazy)
-#use FS::svc_acct_pop;
-
-#for (other) false laziness below
-#use FS::agent;
-#use FS::type_pkgs;
+#probably redundant given the checks below...
+die "access denied"
+ unless $curuser->access_right('New customer')
+ || $curuser->access_right('Edit customer');
my $conf = new FS::Conf;
-my $taxpre = $conf->exists('tax-ship_address') ? 'ship_' : '';
#get record
-my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart, $saved_domsvc);
-my(@invoicing_list);
-my ($ss,$stateid,$payinfo);
+my($custnum, $cust_main, $ss, $stateid, $payinfo, @invoicing_list);
my $same = '';
+my $pkgpart_svcpart = ''; #first_pkg
+my($username, $password, $popnum, $saved_domsvc) = ( '', '', 0, 0 ); #svc_acct
+my %svc_phone = ();
+
if ( $cgi->param('error') ) {
+
$cust_main = new FS::cust_main ( {
map { $_, scalar($cgi->param($_)) } fields('cust_main')
} );
+
$custnum = $cust_main->custnum;
- $saved_domsvc = $cgi->param('domsvc') || '';
- if ( $saved_domsvc =~ /^(\d+)$/ ) {
- $saved_domsvc = $1;
- } else {
- $saved_domsvc = '';
- }
- $saved_pkgpart = $cgi->param('pkgpart_svcpart') || '';
- if ( $saved_pkgpart =~ /^(\d+)_/ ) {
- $saved_pkgpart = $1;
- } else {
- $saved_pkgpart = '';
- }
- $username = $cgi->param('username');
- $password = $cgi->param('_password');
- $popnum = $cgi->param('popnum');
+
+ die "access denied"
+ unless $curuser->access_right($custnum ? 'Edit customer' : 'New customer');
+
@invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
$same = $cgi->param('same');
$cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid');
$ss = $cust_main->ss; # don't mask an entered value on errors
$stateid = $cust_main->stateid; # don't mask an entered value on errors
$payinfo = $cust_main->payinfo; # don't mask an entered value on errors
+
+ $pkgpart_svcpart = $cgi->param('pkgpart_svcpart') || '';
+
+ #svc_acct
+ $username = $cgi->param('username');
+ $password = $cgi->param('_password');
+ $popnum = $cgi->param('popnum');
+ $saved_domsvc = $cgi->param('domsvc') || '';
+ if ( $saved_domsvc =~ /^(\d+)$/ ) {
+ $saved_domsvc = $1;
+ } else {
+ $saved_domsvc = '';
+ }
+
+ #svc_phone
+ $svc_phone{$_} = $cgi->param($_)
+ foreach qw( countrycode phonenum sip_password pin phone_name );
+
} elsif ( $cgi->keywords ) { #editing
+
+ die "access denied"
+ unless $curuser->access_right('Edit customer');
+
my( $query ) = $cgi->keywords;
$query =~ /^(\d+)$/;
$custnum=$1;
@@ -741,31 +276,27 @@ if ( $cgi->param('error') ) {
$paycvv =~ s/./*/g;
$cust_main->paycvv($paycvv);
}
- $saved_pkgpart = 0;
- $saved_domsvc = 0;
- $username = '';
- $password = '';
- $popnum = 0;
@invoicing_list = $cust_main->invoicing_list;
$ss = $cust_main->masked('ss');
$stateid = $cust_main->masked('stateid');
$payinfo = $cust_main->paymask;
-} else {
+
+} else { #new customer
+
+ die "access denied"
+ unless $curuser->access_right('New customer');
+
$custnum='';
$cust_main = new FS::cust_main ( {} );
$cust_main->otaker( &getotaker );
$cust_main->referral_custnum( $cgi->param('referral_custnum') );
- $saved_pkgpart = 0;
- $saved_domsvc = 0;
- $username = '';
- $password = '';
- $popnum = 0;
@invoicing_list = ();
push @invoicing_list, 'POST'
unless $conf->exists('disablepostalinvoicedefault');
$ss = '';
$stateid = '';
$payinfo = '';
+
}
my $error = $cgi->param('error');
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index 8724db9..ad83778 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -1,18 +1,15 @@
%if ( $payby_default eq 'HIDE' ) {
%
% $cust_main->payby('BILL') unless $cust_main->payby;
+% my $payby = $cust_main->payby;
- <INPUT TYPE="hidden" NAME="select" VALUE="<% $cust_main->payby %>">
-
- </FORM>
-
- <FORM NAME="<% $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0">
+ <INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
- <INPUT TYPE="hidden" NAME="payinfo" VALUE="<% $cust_main->paymask %>">
+ <INPUT TYPE="hidden" NAME="<%$payby%>_payinfo" VALUE="<% $cust_main->paymask %>">
% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip paytype paystate )) {
- <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $cust_main->getfield($field) %>">
+ <INPUT TYPE="hidden" NAME="<% $payby.'_'.$field %>" VALUE="<% $cust_main->get($field) %>">
% }
@@ -27,24 +24,18 @@
% die "unrecognized expiration date format: $date";
% }
- <INPUT TYPE="hidden" NAME="exp_month" VALUE="<% $mon %>">
- <INPUT TYPE="hidden" NAME="exp_year" VALUE="<% $year %>">
-
- </FORM>
-
- <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+ <INPUT TYPE="hidden" NAME="<%$payby%>_exp_month" VALUE="<% $mon %>">
+ <INPUT TYPE="hidden" NAME="<%$payby%>_exp_year" VALUE="<% $year %>">
<INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>">
<INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>">
- </FORM>
-
% } else {
%
% my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
- <BR>Billing information
+ <BR><FONT SIZE="+1"><B>Billing information</B></FONT>
<% &ntable("#cccccc") %>
<TR>
@@ -128,13 +119,13 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
% '<TD WIDTH="408">'.
%
% include('/elements/select-month_year.html',
-% 'prefix' => 'exp',
+% 'prefix' => 'CARD_exp',
% 'selected_date' =>
% ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ),
% ).
@@ -145,14 +136,14 @@
%
% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
% qq!</TD>!.
-% '<TD WIDTH="408"><INPUT TYPE="text" NAME="paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'.
+% '<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'.
%
%
% qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!.
% '<TD WIDTH="408">'.
%
% include('/elements/select-month_year.html',
-% 'prefix' => 'paystart',
+% 'prefix' => 'CARD_paystart',
% 'disabled' => $disabled,
% 'empty_option' => 1,
% 'start_year' => 2000,
@@ -167,12 +158,12 @@
% ).
%
% qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!.
-% '<INPUT TYPE="text" NAME="payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!.
+% '<INPUT TYPE="text" NAME="CARD_payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CARD_payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
%
-% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'.
+% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="CARD_payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'.
%
% '</TABLE>',
%
@@ -181,21 +172,21 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!.
-% qq!<TD><INPUT TYPE="text" SIZE=12 NAME="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
-% qq!<TD ALIGN="right">Type</TD><TD><SELECT NAME="paytype">!.
+% qq!<TD><INPUT TYPE="text" SIZE=12 NAME="CHEK_payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
+% qq!<TD ALIGN="right">Type</TD><TD><SELECT NAME="CHEK_paytype">!.
% join('', map { qq!<OPTION VALUE="$_" !.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>" } @FS::cust_main::paytypes).
% qq!</SELECT></TD></TR>!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!.
-% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="CHEK_payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !.
% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
% qq!</TD></TR>!.
%
-% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
-% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+% qq!<INPUT TYPE="hidden" NAME="CHEK_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="CHEK_exp_year" VALUE="2037">!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!.
-% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" NAME="CHEK_payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
% ( $conf->exists('show_bankstate') ?
% qq!<TR><TD ALIGN="right" WIDTH="200">$paystate_label</TD>!.
% qq!<TD COLSPAN="3" WIDTH="408">!.
@@ -203,14 +194,14 @@
% 'empty' => '(choose)',
% 'state' => $cust_main->paystate,
% 'country' => $cust_main->country,
-% 'prefix' => 'pay',
+% 'prefix' => 'CHEK_pay',
% ). "</TD></TR>"
-% : '<INPUT TYPE="hidden" NAME="paystate" VALUE="'.
+% : '<INPUT TYPE="hidden" NAME="CHEK_paystate" VALUE="'.
% $cust_main->paystate. '">'
% ).
%
%
-% qq!<TR><TD COLSPAN=4 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'.
+% qq!<TR><TD COLSPAN=4 WIDTH="608"><INPUT TYPE="checkbox" NAME="CHEK_payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -223,11 +214,11 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="LECB_payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
%
-% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
-% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
-% qq!<INPUT TYPE="hidden" NAME="payname" VALUE="">!.
+% qq!<INPUT TYPE="hidden" NAME="LECB_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="LECB_exp_year" VALUE="2037">!.
+% qq!<INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -243,13 +234,13 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
%
-% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
-% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+% qq!<INPUT TYPE="hidden" NAME="BILL_exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="BILL_exp_year" VALUE="2037">!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="BILL_payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -264,13 +255,13 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE=""></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""></TD></TR>!.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
% '<TD WIDTH="408">'.
%
% include('/elements/select-month_year.html',
-% 'prefix' => 'exp',
+% 'prefix' => 'COMP_exp',
% 'selected_date' =>
% ( $payby eq 'COMP' ? $cust_main->paydate : '' ),
% ).
@@ -290,7 +281,7 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="CASH_paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -306,7 +297,7 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="WEST_paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -322,7 +313,7 @@
% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
%
% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
-% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="MCRD_paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
%
% '<TR><TD>&nbsp;</TD></TR>'.
% '<TR><TD>&nbsp;</TD></TR>'.
@@ -336,57 +327,51 @@
% );
%
% #this should use FS::payby
-% my %allopt = (
-% 'CARD' => 'Credit card',
-% 'CHEK' => 'Electronic check',
-% 'LECB' => 'Phone bill billing',
-% 'BILL' => 'Billing',
-% 'CASH' => 'Cash', # initial payment, then billing',
-% 'WEST' => 'Western Union', # initial payment, then billing',
-% 'MCRD' => 'Manual credit card', # initial payment, then billing',
-% 'COMP' => 'Complimentary',
-% );
-% if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types
-% # when editing customer
+% my @allopt = qw( CARD CHEK LECB BILL CASH WEST MCRD COMP );
+%
+% my %allopt = map { $_ => FS::payby->shortname($_) } @allopt;
+%
+% if ( $cust_main->custnum ) {
+% #don't offer CASH/WEST/MCRD initial payment types when editing customer
% delete $allopt{$_} for qw(CASH WEST MCRD);
% }
%
-% tie my %options, 'Tie::IxHash',
-% map { $_ => $allopt{$_} }
-% grep { exists $allopt{$_} }
-% @payby;
+% my @options = grep exists( $allopt{$_} ), @payby;
%
% my %payby2option = (
-% ( map { $_ => $_ } keys %options ),
+% ( map { $_ => $_ } @options ),
% 'DCRD' => 'CARD',
% 'DCHK' => 'CHEK',
% );
-%
-% my $widget = new HTML::Widgets::SelectLayers(
-% 'options' => \%options,
-% #'form_name' => 'dummy',
-% #'form_action' => 'nothingyet',
-% #chops bottom of page in IE# 'under_position' => 'absolute',
-% 'html_between' => '</TD></TR></TABLE>',
-% 'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] },
-% 'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
-% );
-%
-%
-
-
- <TD WIDTH="408"><% $widget->html %>
- <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+ <TD WIDTH="408">
+ <% include( '/elements/selectlayers.html',
+ 'field' => 'payby',
+ 'curr_value' => $payby2option{$payby || $payby_default || $payby[0] },
+ 'options' => \@options,
+ 'labels' => \%allopt,
+ 'html_between' => '</TD></TR></TABLE>',
+ 'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
+ )
+ %>
<% &ntable("#cccccc") %>
<TR><TD>&nbsp;</TD></TR>
+% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+
<TR>
- <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</TD>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
</TR>
+% foreach my $exempt_group ( @exempt_groups ) {
+% #escape $exempt_group for NAME
+ <TR>
+ <TD WIDTH="608" COLSPAN="2">&nbsp;&nbsp;<INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" VALUE="Y" <% $cust_main->tax_exemption($exempt_group) ? 'CHECKED' : '' %>> Tax Exempt (<% $exempt_group %> taxes)<TD>
+ </TR>
+% }
+
% unless ( $conf->exists('emailinvoiceonly') ) {
<TR>
@@ -426,14 +411,10 @@
<TR>
<TD ALIGN="right" WIDTH="200">Invoice terms </TD>
<TD WIDTH="408">
- <SELECT NAME="invoice_terms">
- <OPTION VALUE="">Default (<% $conf->config('invoice_default_terms') || 'Payable upon receipt' %>)
-% foreach my $term ( 'Payable upon receipt',
-% ( map "Net $_", 0, 10, 15, 30, 45, 60 ),
-% ) {
- <OPTION VALUE="<% $term %>" <% $cust_main->invoice_terms eq $term ? ' SELECTED' : '' %>><% $term %>
-% }
- </SELECT>
+ <% include('/elements/select-terms.html',
+ 'curr_value' => $cust_main->invoice_terms,
+ )
+ %>
</TD>
</TR>
@@ -442,22 +423,40 @@
<TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD>
</TR>
% } else {
-
<INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>">
-% }
+% }
% if ( $conf->exists('voip-cust_cdr_squelch') ) {
<TR>
<TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="squelch_cdr" VALUE="Y" <% $cust_main->squelch_cdr eq "Y" ? 'CHECKED' : '' %>> Omit CDRs from invoices</TD>
</TR>
% } else {
-
<INPUT TYPE="hidden" NAME="squelch_cdr" VALUE="<% $cust_main->squelch_cdr %>">
-% }
+% }
- </TABLE>
+% if ( $conf->exists('voip-cust_email_csv_cdr') ) {
+ <TR>
+ <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="email_csv_cdr" VALUE="Y" <% $cust_main->email_csv_cdr eq "Y" ? 'CHECKED' : '' %>> Attach CDRs as CSV to emailed invoices</TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="email_csv_cdr" VALUE="<% $cust_main->email_csv_cdr %>">
+% }
- </FORM>
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+ <TR>
+ <TD ALIGN="right">CDR termination settlement</TD>
+ <TD><INPUT TYPE = "text"
+ NAME = "cdr_termination_percentage"
+ SIZE = 6
+ VALUE = "<% $cust_main->cdr_termination_percentage %>"
+ STYLE = "text-align:right;"
+ ><B>%</B></TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="cdr_termination_percentage" VALUE="<% $cust_main->cdr_termination_percentage %>">
+% }
+
+ </TABLE>
<% $r %> required fields
% }
@@ -481,4 +480,13 @@ my @payby = grep /\w/, $conf->config('payby');
@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
unless @payby;
+my $show_term = '';
+if ( $cust_main->custnum ) {
+ #false laziness w/view/cust_main/billing.html
+ my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+ my $term_sth = dbh->prepare($term_sql) or die dbh->errstr;
+ $term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+ $show_term = $term_sth->fetchrow_arrayref->[0];
+}
+
</%init>
diff --git a/httemplate/edit/cust_main/birthdate.html b/httemplate/edit/cust_main/birthdate.html
new file mode 100644
index 0000000..415aba3
--- /dev/null
+++ b/httemplate/edit/cust_main/birthdate.html
@@ -0,0 +1,15 @@
+<% ntable("#cccccc", 2) %>
+ <% include ('/elements/tr-input-date-field.html',
+ 'birthdate',
+ $cust_main->birthdate,
+ 'Date of Birth',
+ $conf->config('date_format') || "%m/%d/%Y",
+ 1)
+ %>
+</TABLE>
+<%init>
+
+my( $cust_main, %opt ) = @_;
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/cust_main/bottomfixup.html b/httemplate/edit/cust_main/bottomfixup.html
new file mode 100644
index 0000000..1b29c67
--- /dev/null
+++ b/httemplate/edit/cust_main/bottomfixup.html
@@ -0,0 +1,19 @@
+<% include('/elements/init_overlib.html') %>
+
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-cust_main-address_standardize.html',
+ 'subs' => [ 'address_standardize' ],
+ #'method' => 'POST', #could get too long?
+ )
+%>
+
+<% include( '/elements/xmlhttp.html',
+ 'url' => $p.'misc/xmlhttp-cust_main-censustract.html',
+ 'subs' => [ 'censustract' ],
+ #'method' => 'POST', #could get too long?
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+ <% include('bottomfixup.js') %>
+</SCRIPT>
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
new file mode 100644
index 0000000..1a06d94
--- /dev/null
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -0,0 +1,398 @@
+function bottomfixup(what) {
+
+%# ../cust_main.cgi
+ var layervars = new Array(
+ 'payauto',
+ 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+ 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+ 'paystart_month', 'paystart_year', 'payissue',
+ 'payip',
+ 'paid'
+ );
+
+ var cf = document.CustomerForm;
+ var payby = cf.payby.options[cf.payby.selectedIndex].value;
+ for ( f=0; f < layervars.length; f++ ) {
+ var field = layervars[f];
+ copyelement( cf.elements[payby + '_' + field],
+ cf.elements[field]
+ );
+ }
+
+ //this part does USPS address correction
+
+ // XXX should this be first and should we update the form fields that are
+ // displayed???
+
+ var cf = document.CustomerForm;
+
+ var state_el = cf.elements['state'];
+ var ship_state_el = cf.elements['ship_state'];
+
+ //address_standardize(
+ var cust_main = new Array(
+ 'company', cf.elements['company'].value,
+ 'address1', cf.elements['address1'].value,
+ 'address2', cf.elements['address2'].value,
+ 'city', cf.elements['city'].value,
+ 'state', state_el.options[ state_el.selectedIndex ].value,
+ 'zip', cf.elements['zip'].value,
+
+ 'ship_company', cf.elements['ship_company'].value,
+ 'ship_address1', cf.elements['ship_address1'].value,
+ 'ship_address2', cf.elements['ship_address2'].value,
+ 'ship_city', cf.elements['ship_city'].value,
+ 'ship_state', ship_state_el.options[ ship_state_el.selectedIndex ].value,
+ 'ship_zip', cf.elements['ship_zip'].value
+ );
+
+ address_standardize( cust_main, update_address );
+
+}
+
+var standardize_address;
+
+function update_address(arg) {
+
+ var argsHash = eval('(' + arg + ')');
+
+ var changed = argsHash['address_standardized'];
+ var ship_changed = argsHash['ship_address_standardized'];
+ var error = argsHash['error'];
+ var ship_error = argsHash['ship_error'];
+
+
+ //yay closures
+ standardize_address = function () {
+
+ var cf = document.CustomerForm;
+ var state_el = cf.elements['state'];
+ var ship_state_el = cf.elements['ship_state'];
+
+ if ( changed ) {
+ cf.elements['company'].value = argsHash['new_company'];
+ cf.elements['address1'].value = argsHash['new_address1'];
+ cf.elements['address2'].value = argsHash['new_address2'];
+ cf.elements['city'].value = argsHash['new_city'];
+ setselect(cf.elements['state'], argsHash['new_state']);
+ cf.elements['zip'].value = argsHash['new_zip'];
+ }
+
+ if ( ship_changed ) {
+ cf.elements['ship_company'].value = argsHash['new_ship_company'];
+ cf.elements['ship_address1'].value = argsHash['new_ship_address1'];
+ cf.elements['ship_address2'].value = argsHash['new_ship_address2'];
+ cf.elements['ship_city'].value = argsHash['new_ship_city'];
+ setselect(cf.elements['ship_state'], argsHash['new_ship_state']);
+ cf.elements['ship_zip'].value = argsHash['new_ship_zip'];
+ }
+
+ post_standardization();
+
+ }
+
+
+
+ if ( changed || ship_changed ) {
+
+% if ( $conf->exists('cust_main-auto_standardize_address') ) {
+
+ standardize_address();
+
+% } else {
+
+ // popup a confirmation popup
+
+ var confirm_change =
+ '<CENTER><BR><B>Confirm address standardization</B><BR><BR>' +
+ '<TABLE>';
+
+ if ( changed ) {
+
+ confirm_change = confirm_change +
+ '<TR><TH>Entered billing address</TH>' +
+ '<TH>Standardized billing address</TH></TR>';
+ // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ if ( argsHash['company'] || argsHash['new_company'] ) {
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['company'] +
+ '</TD><TD>' + argsHash['new_company'] + '</TD></TR>';
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['address1'] +
+ '</TD><TD>' + argsHash['new_address1'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['address2'] +
+ '</TD><TD>' + argsHash['new_address2'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['city'] + ', ' + argsHash['state'] + ' ' + argsHash['zip'] +
+ '</TD><TD>' + argsHash['new_city'] + ', ' + argsHash['new_state'] + ' ' + argsHash['new_zip'] + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ }
+
+ if ( ship_changed ) {
+
+ confirm_change = confirm_change +
+ '<TR><TH>Entered service address</TH>' +
+ '<TH>Standardized service address</TH></TR>';
+ // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ if ( argsHash['ship_company'] || argsHash['new_ship_company'] ) {
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['ship_company'] +
+ '</TD><TD>' + argsHash['new_ship_company'] + '</TD></TR>';
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' + argsHash['ship_address1'] +
+ '</TD><TD>' + argsHash['new_ship_address1'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['ship_address2'] +
+ '</TD><TD>' + argsHash['new_ship_address2'] + '</TD></TR>' +
+ '<TR><TD>' + argsHash['ship_city'] + ', ' + argsHash['ship_state'] + ' ' + argsHash['ship_zip'] +
+ '</TD><TD>' + argsHash['new_ship_city'] + ', ' + argsHash['new_ship_state'] + ' ' + argsHash['new_ship_zip'] + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ }
+
+ var addresses = 'address';
+ var height = 268;
+ if ( changed && ship_changed ) {
+ addresses = 'addresses';
+ height = 396; // #what
+ }
+
+ confirm_change = confirm_change +
+ '<TR><TD>' +
+ '<BUTTON TYPE="button" onClick="post_standardization();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' +
+ '</TD><TD>' +
+ '<BUTTON TYPE="button" onClick="standardize_address();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' +
+ '</TD></TR>' +
+ '<TR><TD COLSPAN=2 ALIGN="center">' +
+ '<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>';
+
+ overlib( confirm_change, CAPTION, 'Confirm address standardization', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, height, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+% }
+
+ } else {
+
+ post_standardization();
+
+ }
+
+
+}
+
+function post_standardization() {
+
+ var cf = document.CustomerForm;
+
+% if ( $conf->exists('enable_taxproducts') ) {
+
+ 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;
+
+ if ( country == 'CA' || country == 'US' ) {
+
+ var state_el = cf.elements['<% $taxpre %>state'];
+ var state = state_el.options[ state_el.selectedIndex ].value;
+
+ var url = "cust_main/choose_tax_location.html" +
+ "?data_vendor=cch-zip" +
+ ";city=" + cf.elements['<% $taxpre %>city'].value +
+ ";state=" + state +
+ ";zip=" + cf.elements['<% $taxpre %>zip'].value +
+ ";country=" + country +
+ ";geocode=" + geocode +
+ ";";
+
+ // popup a chooser
+ OLgetAJAX( url, update_geocode, 300 );
+
+ } else {
+
+ cf.elements['geocode'].value = 'DEFAULT';
+ post_geocode();
+
+ }
+
+ } else {
+
+ post_geocode();
+
+ }
+
+% } else {
+
+ post_geocode();
+
+% }
+
+}
+
+function post_geocode() {
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ //alert('fetch census tract data');
+ var cf = document.CustomerForm;
+ var state_el = cf.elements['ship_state'];
+ var census_data = new Array(
+ 'year', <% $conf->config('census_year') || '2009' %>,
+ 'address', cf.elements['ship_address1'].value,
+ 'city', cf.elements['ship_city'].value,
+ 'state', state_el.options[ state_el.selectedIndex ].value,
+ 'zip', cf.elements['ship_zip'].value
+ );
+
+ censustract( census_data, update_censustract );
+
+% }else{
+
+ document.CustomerForm.submit();
+
+% }
+
+}
+
+function update_geocode() {
+
+ //yay closures
+ set_geocode = function (what) {
+
+ var cf = document.CustomerForm;
+
+ //alert(what.options[what.selectedIndex].value);
+ var argsHash = eval('(' + what.options[what.selectedIndex].value + ')');
+ 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'];
+ 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 );
+
+}
+
+var set_censustract;
+
+function update_censustract(arg) {
+
+ var argsHash = eval('(' + arg + ')');
+
+ var cf = document.CustomerForm;
+
+ var msacode = argsHash['msacode'];
+ var statecode = argsHash['statecode'];
+ var countycode = argsHash['countycode'];
+ var tractcode = argsHash['tractcode'];
+ var error = argsHash['error'];
+
+ var newcensus =
+ new String(statecode) +
+ new String(countycode) +
+ new String(tractcode).replace(/\s$/, ''); // JSON 1 workaround
+
+ set_censustract = function () {
+
+ cf.elements['censustract'].value = newcensus
+ cf.submit();
+
+ }
+
+ if (error || cf.elements['censustract'].value != newcensus) {
+ // popup an entry dialog
+
+ if (error) { newcensus = error; }
+ newcensus.replace(/.*ndefined.*/, 'Not found');
+
+ var choose_censustract =
+ '<CENTER><BR><B>Confirm censustract</B><BR>' +
+ '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
+ 'census_year=<% $conf->config('census_year') || '2008' %>' +
+ '&latitude=' + cf.elements['latitude'].value +
+ '&longitude=' + cf.elements['longitude'].value +
+ '" target="_blank">Map service module location</A><BR>' +
+ '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
+ 'census_year=<% $conf->config('census_year') || '2008' %>' +
+ '&zip_code=' + cf.elements['ship_zip'].value +
+ '" target="_blank">Map zip code center</A><BR><BR>' +
+ '<TABLE>';
+
+ choose_censustract = choose_censustract +
+ '<TR><TH style="width:50%">Entered census tract</TH>' +
+ '<TH style="width:50%">Calculated census tract</TH></TR>' +
+ '<TR><TD>' + cf.elements['censustract'].value +
+ '</TD><TD>' + newcensus + '</TD></TR>' +
+ '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+
+ choose_censustract = choose_censustract +
+ '<TR><TD ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="document.CustomerForm.submit();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract </BUTTON>' +
+ '</TD><TD ALIGN="center">' +
+ '<BUTTON TYPE="button" onClick="set_censustract();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract </BUTTON>' +
+ '</TD></TR>' +
+ '<TR><TD COLSPAN=2 ALIGN="center">' +
+ '<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>';
+
+ overlib( choose_censustract, CAPTION, 'Confirm censustract', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+ } else {
+
+ cf.submit();
+
+ }
+
+}
+
+function copyelement(from, to) {
+ if ( from == undefined ) {
+ to.value = '';
+ } else if ( from.type == 'select-one' ) {
+ to.value = from.options[from.selectedIndex].value;
+ //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
+ } else if ( from.type == 'checkbox' ) {
+ if ( from.checked ) {
+ to.value = from.value;
+ } else {
+ to.value = '';
+ }
+ } else {
+ if ( from.value == undefined ) {
+ to.value = '';
+ } else {
+ to.value = from.value;
+ }
+ }
+ //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
+}
+
+function setselect(el, value) {
+
+ for ( var s = 0; s < el.options.length; s++ ) {
+ if ( el.options[s].value == value ) {
+ el.selectedIndex = s;
+ }
+ }
+
+}
+<%init>
+
+my $conf = new FS::Conf;
+
+my $taxpre = $conf->exists('tax-ship_address') ? 'ship_' : '';
+
+</%init>
diff --git a/httemplate/edit/cust_main/choose_tax_location.html b/httemplate/edit/cust_main/choose_tax_location.html
index bd8b95c..ac475c5 100644
--- a/httemplate/edit/cust_main/choose_tax_location.html
+++ b/httemplate/edit/cust_main/choose_tax_location.html
@@ -1,5 +1,6 @@
<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 %>">
@@ -18,7 +19,7 @@
% foreach qw( city county state );
% $content .= $location->cityflag eq 'I' ? 'Y' : 'N' ;
% my $selected = '' ;
-% if (!$have_selected && lc($location->city) eq lc($location{city})) {
+% if ($geocode && $location->geocode eq $geocode) {
% $selected = 'SELECTED';
% }
<OPTION VALUE="<% $value %>" STYLE="<% $style %>" <% $selected %>><% $content %>
@@ -26,8 +27,8 @@
</SELECT><BR><BR>
<TABLE><TR>
- <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes')); document.bottomform.submit();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
- <TD><BUTTON TYPE="button" onClick="document.bottomform.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD>
+ <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>
@@ -36,7 +37,6 @@
<%init>
my $conf = new FS::Conf;
-my $have_selected = 0;
my %location = ();
@@ -46,6 +46,8 @@ my %location = ();
($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
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
index 27dd385..3ccee62 100644
--- a/httemplate/edit/cust_main/contact.html
+++ b/httemplate/edit/cust_main/contact.html
@@ -32,6 +32,7 @@
'disabled' => $disabled,
'same_checked' => $opt{'same_checked'},
'geocode' => $opt{'geocode'},
+ 'censustract' => $opt{'censustract'},
)
%>
@@ -110,6 +111,12 @@ $cust_main->set($pre.'state', $statedefault )
$cust_main->set('stateid_state', $cust_main->state )
unless $pre || $cust_main->get('stateid_state');
+$opt{geocode} ||= $cust_main->get('geocode');
+
+if ( $conf->exists('cust_main-require_censustract') ) {
+ $opt{censustract} ||= $cust_main->censustract;
+}
+
#my($county_html, $state_html, $country_html) =
# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'),
# $cust_main->get($pre.'state'),
diff --git a/httemplate/edit/cust_main/first_pkg.html b/httemplate/edit/cust_main/first_pkg.html
new file mode 100644
index 0000000..0de33c0
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg.html
@@ -0,0 +1,55 @@
+% if ( @part_pkg ) {
+
+ <BR><BR>
+ <FONT SIZE="+1"><B>First package</B></FONT>
+ <% ntable("#cccccc") %>
+
+ <TR>
+ <TD COLSPAN=2>
+ <% include('first_pkg/select-part_pkg.html',
+ 'part_pkg' => \@part_pkg,
+ %opt,
+ # map { $_ => $opt{$_} } qw( pkgpart_svcpart saved_domsvc )
+ )
+ %>
+
+% }
+<%init>
+
+my( $cust_main, %opt ) = @_;
+
+# pry the wrong place for this logic. also pretty expensive
+
+#false laziness, copied from FS::cust_pkg::order
+my $pkgpart;
+my $agentnum = '';
+my @agents = $FS::CurrentUser::CurrentUser->agents;
+if ( scalar(@agents) == 1 ) {
+ # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
+ $pkgpart = $agents[0]->pkgpart_hashref;
+ $agentnum = $agents[0]->agentnum;
+} else {
+ #can't know (agent not chosen), so, allow all
+ $agentnum = 'all';
+ my %typenum;
+ foreach my $agent ( @agents ) {
+ next if $typenum{$agent->typenum}++;
+ $pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
+ }
+}
+#eslaf
+
+my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+my @part_pkg =
+ grep { $_->svcpart(\@first_svc)
+ && ( $pkgpart->{ $_->pkgpart }
+ || $agentnum eq 'all'
+ || ( $agentnum ne 'all' && $agentnum && $_->agentnum
+ && $_->agentnum == $agentnum
+ )
+ )
+ }
+ qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/select-part_pkg.html b/httemplate/edit/cust_main/first_pkg/select-part_pkg.html
new file mode 100644
index 0000000..871e1cd
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/select-part_pkg.html
@@ -0,0 +1,170 @@
+<% include('/elements/xmlhttp.html',
+ 'url' => $url_prefix.'misc/svc_acct-domains.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_domains' ],
+ )
+%>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $url_prefix.'misc/part_svc-columns.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_part_svc' ],
+ )
+%>
+
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="">
+
+<SCRIPT TYPE="text/javascript">
+
+ function selopt(what,value,text,selected) {
+ var optionName = new Option(text, value, false, selected);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ var pkgpart_svcpart2svcdb = {
+% foreach my $pkgpart ( map $_->pkgpart, @part_pkg ) {
+ "<% $pkgpart_svcpart{$pkgpart} %>":"<% $svcdb{$pkgpart} %>",
+% }
+ '':''
+ };
+
+ function <% $opt{'prefix'} %>pkgpart_svcpart_changed_too(what,selected) {
+
+ <% $opt{'onchange'} %>;
+
+ pkgpart_svcpart = what.options[what.selectedIndex].value;
+
+ var svcdb = pkgpart_svcpart2svcdb[pkgpart_svcpart];
+
+ what.form.svcdb.value = svcdb;
+
+ if ( svcdb == 'svc_acct' ) {
+
+ // go get the new domains
+ function <% $opt{'prefix'} %>update_domains(domains) {
+
+ // blank the current domain list
+ for ( var i = what.form.<% $opt{'prefix'} %>domsvc.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>domsvc.options[i] = null;
+
+ // add the new domains
+ var domainArray = eval('(' + domains + ')' );
+ for ( var s = 0; s < domainArray.length; s=s+2 ) {
+ var domainLabel = domainArray[s+1];
+ if ( domainLabel == "" )
+ domainLabel = '(n/a)';
+ selopt( what.form.<% $opt{'prefix'} %>domsvc,
+ domainArray[s],
+ domainLabel,
+ (domainArray[s] == selected) ? true : false
+ );
+ }
+
+ }
+
+ <% $opt{'prefix'} %>get_domains( pkgpart_svcpart,
+ <% $opt{'prefix'} %>update_domains
+ );
+
+ } else if ( svcdb == 'svc_phone' ) {
+
+ function <% $opt{'prefix'} %>update_svc_phone(part_svc_column) {
+ var colArray = eval('(' + part_svc_column + ')' );
+ for ( var s = 0; s < colArray.length; s=s+3 ) {
+ var name = colArray[s];
+ var flag = colArray[s+1];
+ var value = colArray[s+2];
+ var td_label = document.getElementById(name+'_label_td');
+ var td = document.getElementById(name+'_td');
+ var input = document.getElementById(name);
+ if ( flag == 'D' ) {
+ if ( ! input.value ) { input.value = value; }
+ td_label.style.display = ''
+ td.style.display = ''
+ } else if ( flag == 'F' ) {
+ input.value = value;
+ td_label.style.display = 'none'
+ td.style.display = 'none'
+ } else {
+ td_label.style.display = ''
+ td.style.display = ''
+ }
+ }
+ }
+
+ <% $opt{'prefix'} %>get_part_svc( pkgpart_svcpart,
+ <% $opt{'prefix'} %>update_svc_phone
+ );
+
+ }
+
+ }
+
+</SCRIPT>
+
+<% include( '/elements/selectlayers.html',
+ 'field' => $opt{'prefix'}. 'pkgpart_svcpart',
+ 'curr_value' => $opt{pkgpart_svcpart},
+ 'options' => \@options,
+ 'labels' => \%labels,
+ 'html_between' => '</TD></TR></TABLE>',
+ #'onchange' => $opt{'prefix'}. 'pkgpart_svcpart_changed(this,0);',
+ 'onchange' => $opt{'prefix'}. 'pkgpart_svcpart_changed_too(what,0)',
+
+ 'layer_callback' => $layer_callback,
+ 'layermap' => \%layermap,
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+ pkgpart_svcpart_changed_too( document.CustomerForm.pkgpart_svcpart,
+ <% $opt{saved_domsvc} %>
+ );
+</SCRIPT>
+
+<%init>
+
+my %opt = @_;
+
+foreach my $opt (qw( svc_part pkgparts saved_pkgpart saved_domsvc prefix)) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+$opt{saved_domsvc} = 0 unless $opt{saved_domsvc};
+
+my $url_prefix = $opt{'relurls'} ? '' : $p;
+
+my @part_pkg = @{$opt{'part_pkg'}};
+
+my @first_svc = ( 'svc_acct', 'svc_phone' );
+
+my %pkgpart_svcpart = ();
+my %svcdb = ();
+my %layermap = ();
+foreach my $part_pkg ( @part_pkg ) {
+ my $pkgpart = $part_pkg->pkgpart;
+ my $pkgpart_svcpart = $pkgpart. "_". $part_pkg->svcpart(\@first_svc);
+ $pkgpart_svcpart{$pkgpart} = $pkgpart_svcpart;
+ $svcdb{$pkgpart} = $part_pkg->part_svc(\@first_svc)->svcdb;
+ $layermap{$pkgpart_svcpart} = $svcdb{$pkgpart};
+}
+
+my @options = ( '', map $pkgpart_svcpart{ $_->pkgpart }, @part_pkg );
+my %labels = ( '' => ( $opt{'empty_label'} || '(none)' ),
+ map { $pkgpart_svcpart{ $_->pkgpart } => $_->pkg_comment }
+ @part_pkg
+ );
+
+my $layer_callback = sub {
+ my $layer = shift;
+ #$layer_fields, $layer_values, $layer_prefix
+
+# my( $pkgpart, $svcpart ) = split('_', $layer);
+# my $svcdb = $svcdb{$pkgpart};
+ my $svcdb = $layer;
+
+ return '' unless $svcdb; #'<BR><BR><BR><BR><BR>'
+
+ #full path cause we're being slung around as a coderef (mason closures?)
+ include("/edit/cust_main/first_pkg/$svcdb.html", %opt, );
+};
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/svc_acct.html b/httemplate/edit/cust_main/first_pkg/svc_acct.html
new file mode 100644
index 0000000..150d4c0
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/svc_acct.html
@@ -0,0 +1,88 @@
+<% ntable("#cccccc") %>
+
+ <TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "username"
+ VALUE = "<% $opt{'username'} %>"
+ SIZE = <% $ulen2 %>
+ MAXLENGTH = <% $ulen %>
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+ <SELECT NAME="domsvc">
+ <OPTION>(none)</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Password</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "_password"
+ VALUE = "<% $opt{'password'} %>"
+ SIZE = <% $pmax2 %>
+ MAXLENGTH = <% $passwordmax %>>
+% unless ( $opt{'password_verify'} ) {
+ (blank to generate)
+% }
+ </TD>
+ </TR>
+
+% if ( $opt{'password_verify'} ) {
+ <TR>
+ <TD ALIGN="right">Re-enter Password</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "_password2"
+ VALUE = "<% $opt{'password2'} %>"
+ SIZE = <% $pmax2 %>
+ MAXLENGTH = <% $passwordmax %>>
+ </TD>
+ </TR>
+% }
+
+% if ( $conf->exists('security_phrase') ) {
+ <TR>
+ <TD ALIGN="right">Security Phrase</TD>
+ <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $opt{'sec_phrase'} %>">
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">
+% }
+
+% if ( $conf->exists('svc_acct-disable_access_number') ) {
+ <INPUT TYPE="hidden" NAME="popnum" VALUE="">
+% } else {
+ <TR>
+ <TD ALIGN="right">Access number</TD>
+%# XXX should gain "area code" selection and labels on the dropdowns
+ <TD><% FS::svc_acct_pop::popselector($opt{'popnum'}) %></TD>
+ </TR>
+% }
+
+</TABLE>
+
+<%init>
+
+#use FS::svc_acct_pop;
+
+my( %opt ) = @_;
+
+my $conf = new FS::Conf;
+
+#false laziness: (mostly) copied from edit/svc_acct.cgi
+#$ulen = $svc_acct->dbdef_table->column('username')->length;
+my $ulen = dbdef->table('svc_acct')->column('username')->length;
+my $ulen2 = $ulen+2;
+my $passwordmax = $conf->config('passwordmax') || 8;
+my $pmax2 = $passwordmax + 2;
+
+</%init>
diff --git a/httemplate/edit/cust_main/first_pkg/svc_phone.html b/httemplate/edit/cust_main/first_pkg/svc_phone.html
new file mode 100644
index 0000000..70e013e
--- /dev/null
+++ b/httemplate/edit/cust_main/first_pkg/svc_phone.html
@@ -0,0 +1,82 @@
+<% ntable("#cccccc") %>
+
+%#XXX this should be hidden or something in most/all cases
+ <TR>
+ <TD ALIGN="right" ID="countrycode_label_td">Country code</TD>
+ <TD ID="countrycode_td">
+ <INPUT TYPE = "text"
+ NAME = "countrycode"
+ ID = "countrycode"
+ VALUE = "<% $opt{'countrycode'} %>"
+ SIZE = 4
+ MAXLENGTH = 3
+ >
+ </TD>
+ </TR>
+
+%#we don't know the svcpart until the dropdown is changed :/
+%#<% include('/elements/tr-select-did.html',
+%# 'label' => 'Phone number',
+%# 'curr_value' => $opt{'phonenum'},
+%# )
+%#%>
+ <TR>
+ <TD ALIGN="right" ID="phonenum_label_td">Phone Number</TD>
+ <TD ID="phonenum_td">
+ <INPUT TYPE = "text"
+ NAME = "phonenum"
+ ID = "phonenum"
+ VALUE = "<% $opt{'phonenum'} %>"
+ SIZE = 21
+ MAXLENGTH = 20
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" ID="sip_password_label_td">SIP password</TD>
+ <TD ID="sip_password_td">
+ <INPUT TYPE = "text"
+ NAME = "sip_password"
+ ID = "sip_password"
+ VALUE = "<% $opt{'sip_password'} %>"
+ MAXLENGTH = 80
+ >
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" ID="pin_label_td">Voicemail PIN</TD>
+ <TD ID="pin_td">
+ <INPUT TYPE = "text"
+ NAME = "pin"
+ ID = "pin"
+ VALUE = "<% $opt{'pin'} %>"
+ SIZE = 5
+ MAXLENGTH = 4
+ >
+ </TD>
+ </TR>
+
+%#XXX this should be hidden or something in most/all cases
+ <TR>
+ <TD ALIGN="right" ID="phone_name_label_td">Name</TD>
+ <TD ID="phone_name_td">
+ <INPUT TYPE = "text"
+ NAME = "phone_name"
+ ID = "phone_name"
+ VALUE = "<% $opt{'phone_name'} %>"
+ MAXLENGTH = 80
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+<%init>
+
+my( %opt ) = @_;
+
+#my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/cust_main/select-domain.html b/httemplate/edit/cust_main/select-domain.html
deleted file mode 100644
index bec1e83..0000000
--- a/httemplate/edit/cust_main/select-domain.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-<% include('/elements/xmlhttp.html',
- 'url' => $p.'misc/svc_acct-domains.cgi',
- 'subs' => [ $opt{'prefix'}. 'get_domains' ],
- )
-%>
-
-<SCRIPT TYPE="text/javascript">
-
- function selopt(what,value,text,selected) {
- var optionName = new Option(text, value, false, selected);
- var length = what.length;
- what.options[length] = optionName;
- }
-
- function <% $opt{'prefix'} %>pkgpart_svcpart_changed(what,selected) {
-
- pkgpart_svcpart = what.options[what.selectedIndex].value;
-
- function <% $opt{'prefix'} %>update_domains(domains) {
-
- // blank the current domain list
- for ( var i = what.form.<% $opt{'prefix'} %>domsvc.length; i >= 0; i-- )
- what.form.<% $opt{'prefix'} %>domsvc.options[i] = null;
-
- // add the new domains
- var domainArray = eval('(' + domains + ')' );
- for ( var s = 0; s < domainArray.length; s=s+2 ) {
- var domainLabel = domainArray[s+1];
- if ( domainLabel == "" )
- domainLabel = '(n/a)';
- selopt(what.form.<% $opt{'prefix'} %>domsvc, domainArray[s], domainLabel, (domainArray[s] == selected) ? true : false);
- }
-
- }
-
- // go get the new domains
- <% $opt{'prefix'} %>get_domains( pkgpart_svcpart, <% $opt{'prefix'} %>update_domains );
-
- }
-
-</SCRIPT>
-
-<SELECT NAME="<% $opt{'prefix'} %>pkgpart_svcpart" onchange="<% $opt{'prefix'} %>pkgpart_svcpart_changed(this,0);" >
- <OPTION VALUE="">(none)
-
-% foreach my $part_pkg ( @part_pkg ) {
-
- <OPTION VALUE="<% $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct') %>"<% ( $opt{saved_pkgpart} && $part_pkg->pkgpart == $opt{saved_pkgpart} ) ? ' SELECTED' : '' %>><% $part_pkg->pkg. " - ". $part_pkg->comment %>
-
-% }
-
-</SELECT>
-<SCRIPT>
- pkgpart_svcpart_changed(document.bottomform.pkgpart_svcpart, <% $opt{saved_domsvc} %>);
-</SCRIPT>
-
-<%init>
-my %opt = @_;
-foreach my $opt (qw( svc_part pkgparts saved_pkgpart saved_domsvc prefix)) {
- $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
-}
-$opt{saved_domsvc} = 0 unless $opt{saved_domsvc};
-my @part_pkg = @{$opt{'pkgparts'}};
-
-</%init>
-
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
new file mode 100644
index 0000000..0410506
--- /dev/null
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -0,0 +1,97 @@
+<% &ntable("#cccccc") %>
+
+%# agent
+<% include('/elements/tr-select-agent.html',
+ 'curr_value' => $cust_main->agentnum,
+ 'label' => "<B>${r}Agent</B>",
+ 'empty_label' => 'Select agent',
+ 'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
+ )
+%>
+
+%# agent_custid
+% if ( $conf->exists('cust_main-edit_agent_custid') ) {
+
+ <TR>
+ <TD ALIGN="right">Customer identifier</TD>
+ <TD><INPUT TYPE="text" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>"></TD>
+ </TR>
+
+% } else {
+
+ <INPUT TYPE="hidden" NAME="agent_custid" VALUE="<% $cust_main->agent_custid %>">
+
+% }
+
+%# referral (advertising source)
+%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0;
+%if ( $custnum && ! $conf->exists('editreferrals') ) {
+
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>">
+
+% } else {
+
+ <% include('/elements/tr-select-part_referral.html',
+ 'curr_value' => $refnum
+ )
+ %>
+% }
+
+
+%# referring customer
+%my $referring_cust_main = '';
+%if ( $cust_main->referral_custnum
+% and $referring_cust_main =
+% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+%) {
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A>
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>">
+% } elsif ( ! $conf->exists('disable_customer_referrals') ) {
+
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> -->
+ <% include('/elements/search-cust_main.html',
+ 'field_name' => 'referral_custnum',
+ )
+ %>
+ </TD>
+ </TR>
+% } else {
+
+
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">
+% }
+
+%# signup date
+% if ( $conf->exists('cust_main-edit_signupdate') ) {
+ <% include('/elements/tr-input-date-field.html', {
+ 'name' => 'signupdate',
+ 'value' => $cust_main->signupdate,
+ 'label' => 'Signup date',
+ 'format' => $conf->config('date_format') || "%m/%d/%Y",
+ })
+ %>
+% }
+
+</TABLE>
+
+<%init>
+
+my( $cust_main, %opt ) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $conf = new FS::Conf;
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main_attach.cgi b/httemplate/edit/cust_main_attach.cgi
new file mode 100755
index 0000000..43d2e29
--- /dev/null
+++ b/httemplate/edit/cust_main_attach.cgi
@@ -0,0 +1,59 @@
+<% include('/elements/header-popup.html', "$action File Attachment") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/cust_main_attach.cgi" METHOD=POST ENCTYPE="multipart/form-data">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="attachnum" VALUE="<% $attachnum %>">
+
+<BR><BR>
+
+% if(defined $attach) {
+Filename <INPUT TYPE="text" NAME="filename" VALUE="<% $attach->filename %>"><BR>
+MIME type <INPUT TYPE="text" NAME="mime_type" VALUE="<% $attach->mime_type %>"<BR>
+Size: <% $attach->size %><BR>
+
+% }
+% else { # !defined $attach
+
+Filename <INPUT TYPE="file" NAME="file"><BR>
+
+% }
+
+<BR>
+<INPUT TYPE="submit" NAME="submit"
+ VALUE="<% $attachnum ? "Apply Changes" : "Upload File" %>">
+
+% if(defined $attach and $curuser->access_right('Delete attachment')) {
+<BR>
+<INPUT TYPE="submit" NAME="delete" value="Delete File">
+% }
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $attachnum = '';
+my $attach;
+if ( $cgi->param('error') ) {
+ #$comment = $cgi->param('comment');
+} elsif ( $cgi->param('attachnum') =~ /^(\d+)$/ ) {
+ $attachnum = $1;
+ die "illegal query ". $cgi->keywords unless $attachnum;
+ $attach = qsearchs('cust_attachment', { 'attachnum' => $attachnum });
+ die "no such attachment: ". $attachnum unless $attach;
+}
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die "illegal custnum";
+my $custnum = $1;
+
+my $action = $attachnum ? 'Edit' : 'Add';
+
+die "access denied"
+ unless $curuser->access_right("$action attachment");
+
+</%init>
+
diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi
index 3c28774..07e5198 100755
--- a/httemplate/edit/cust_pay.cgi
+++ b/httemplate/edit/cust_pay.cgi
@@ -72,6 +72,16 @@ Payment
% }
</TR>
+% if ( $conf->exists('pkg-balances') ) {
+ <% include('/elements/tr-select-cust_pkg-balances.html',
+ 'custnum' => $custnum,
+ 'cgi' => $cgi
+ )
+ %>
+% } else {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% }
+
</TABLE>
<BR>
@@ -95,7 +105,7 @@ my $money_char = $conf->config('money_char') || '$';
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Post payment');
-my($link, $linknum, $paid, $payby, $payinfo, $_date);
+my($link, $linknum, $paid, $payby, $payinfo, $_date);
if ( $cgi->param('error') ) {
$link = $cgi->param('link');
$linknum = $cgi->param('linknum');
@@ -131,7 +141,7 @@ if ( $link eq 'invnum' ) {
my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } )
or die "unknown invnum $linknum";
$custnum = $cust_bill->custnum;
-} elsif ( $link eq 'custnum' ) {
+} elsif ( $link eq 'custnum' || $link eq 'popup' ) {
$custnum = $linknum;
}
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
index f927e10..dd1ed33 100755
--- a/httemplate/edit/cust_pkg.cgi
+++ b/httemplate/edit/cust_pkg.cgi
@@ -128,10 +128,10 @@ my %all_comment = ();
#}
foreach (qsearch('part_pkg', {} )) {
$all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
- $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+ $all_comment{ $_ -> getfield('pkgpart') } = $_->custom_comment;
next if $_->disabled;
$pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
- $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+ $comment{ $_ -> getfield('pkgpart') } = $_->custom_comment;
}
my($custnum, %remove_pkg);
diff --git a/httemplate/edit/cust_tax_adjustment.html b/httemplate/edit/cust_tax_adjustment.html
new file mode 100644
index 0000000..9d4afbc
--- /dev/null
+++ b/httemplate/edit/cust_tax_adjustment.html
@@ -0,0 +1,102 @@
+<% include('/elements/header-popup.html', 'Tax adjustment' ) %>
+
+<% include('/elements/error.html') %>
+
+<SCRIPT TYPE="text/javascript">
+
+function enable_tax_adjustment () {
+ if ( document.TaxAdjustmentForm.amount.value
+ && document.TaxAdjustmentForm.taxname.selectedIndex > 0 ) {
+ document.TaxAdjustmentForm.submit.disabled = false;
+ } else {
+ document.TaxAdjustmentForm.submit.disabled = true;
+ }
+}
+
+function validate_tax_adjustment () {
+ var comment = document.TaxAdjustmentForm.comment.value;
+ var comment_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ;
+ var amount = document.TaxAdjustmentForm.amount.value;
+ var amount_regex = /^\s*\$?\s*(\d*(\.?\d{1,2}))\s*$/ ;
+ var rval = true;
+
+ if ( ! amount_regex.test(amount) ) {
+ alert('Illegal amount - enter the amount of the tax adjustment, for example, "5" or "43" or "21.46".');
+ return false;
+ }
+ if ( ! comment_regex.test(comment) ) {
+ alert('Illegal comment - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' );
+ return false;
+ }
+
+ return true;
+}
+
+</SCRIPT>
+
+<FORM ACTION="process/cust_tax_adjustment.html" NAME="TaxAdjustmentForm" ID="TaxAdjustmentForm" METHOD="POST" onsubmit="document.TaxAdjustmentForm.submit.disabled=true;return validate_tax_adjustment();">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+<TABLE ID="TaxAdjustmentTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc">
+
+<TR>
+ <TD ALIGN="right">Tax </TD>
+ <TD>
+ <SELECT NAME="taxname" ID="taxname" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ <OPTION VALUE=""></OPTION>
+% foreach my $taxname (@taxname) {
+ <OPTION VALUE="<% $taxname %>"><% $taxname %></OPTION>
+% }
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount </TD>
+ <TD>
+ $<INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Comment </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="comment" SIZE="50" MAXLENGTH="50" VALUE="<% $comment %>" onChange="enable_tax_adjustment()" onKeyPress="enable_tax_adjustment()">
+ </TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" ID="submit" NAME="submit" VALUE="Add tax adjustment" <% $cgi->param('error') ? '' :' DISABLED' %>>
+
+</FORM>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $sql = 'SELECT DISTINCT(taxname) FROM cust_main_county';
+my $sth = dbh->prepare($sql) or die dbh->errstr;
+$sth->execute() or die $sth->errstr;
+my @taxname = map { $_->[0] || 'Tax' } @{ $sth->fetchall_arrayref([]) };
+
+my $conf = new FS::Conf;
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $amount = '';
+if ( $cgi->param('amount') =~ /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ) {
+ $amount = $1;
+}
+
+$cgi->param('comment') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
+ or die 'illegal description';
+my $comment = $1;
+
+</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index d18a37d..fd73e03 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -132,37 +132,39 @@ Example:
# initialization callbacks
###
- ###global callbacks
+ ###global callbacks, always run if provided
- #always run if provided, after decoding long CGI "redirect=" responses but
+ #after decoding long CGI "redirect=" responses but
# before object creation/search
# (useful if you have a long form that might trigger redirect= and you need
# to do things with $cgi params - they're not decoded in the calling
# <%init> block yet)
'begin_callback' = sub { my( $cgi, $fields_listref, $opt_hashref ) = @_; },
- #always run, after the mode-specific object creation/search
+ #after the mode-specific object creation/search
'end_callback' = sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
- ###mode-specific callbacks
+ ###mode-specific callbacks. one (and only one) of these four is called
+ #run when adding
+ 'new_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ #run when editing
+ 'edit_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
#run when re-displaying with an error
'error_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
- #run when editing
- 'edit_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
-
+ #run when cloning
+ 'clone_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
+
+ ###callbacks called in new mode only
+
# returns a hashref for the new object
'new_hashref_callback'
# returns the new object iself (otherwise, ->new is called)
'new_object_callback'
-
- #run when adding
- 'new_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
-
- #run when cloning
- 'clone_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; },
###display callbacks
@@ -287,7 +289,8 @@ Example:
% foreach grep exists($f->{$_}), qw( hashref agent_virt agent_null_right );
%
% if ( $type eq 'tablebreak-tr-title' ) {
-% $include_common{'table_id'} = 'TableNumber'. $tablenum++
+% $include_common{'table_id'} = 'TableNumber'. $tablenum++;
+% $include_common{'colspan'} = $f->{colspan} if $f->{colspan};
% }
%
% my $layer_prefix_on = '';
@@ -315,6 +318,27 @@ Example:
% @include;
% };
%
+% my $column_sub = sub {
+% my %opt = @_;
+%
+% my $column = delete($opt{field});
+% my $fieldnum = delete($opt{fieldnum});
+% my $include = delete($opt{type}) || 'text';
+% $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
+%
+% ( "/elements/$include.html",
+% 'field' => $field.'__'.$column.$fieldnum,
+% 'id' => $field.'__'.$column.$fieldnum,
+% 'layer_prefix' => $field.'__'.$column.$fieldnum.".",
+% ( $fieldnum
+% ? ('cell_style' => 'border-top:1px solid black')
+% : ()
+% ),
+% 'cgi' => $cgi,
+% %opt,
+% );
+% };
+%
% unless ( $type =~ /^column/ ) {
% $g_row = 1 if $type eq 'tablebreak-tr-title';
% $g_row++;
@@ -380,8 +404,35 @@ Example:
% 'layer_values' => $layer_values,
% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ),
% );
+% $existing[0] =~ s(^/elements/tr-)(/elements/);
+% my @label = @existing;
+% $label[0] = '/elements/tr-td-label.html';
+ <% include( @label ) %>
+ <TD>
<% include( @existing ) %>
+ </TD>
+
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }( %$c,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $name_obj->$column()
+% );
+
+ <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>'
+ style='text-align:right;vertical-align:top;
+ border-top:1px solid black;padding-top:5px;'>
+ <% $c->{'label'} || '' %>
+ </TD>
+ <TD style='border-top:1px solid black;padding-top:3px;'>
+ <% include( @column ) %>
+ </TD>
+% }
+% }
+
+ </TR>
% $fieldnum++;
% $g_row++;
@@ -407,9 +458,40 @@ Example:
% 'onchange' => $onchange,
% ( $fieldnum ? ('cell_style' => 'border-top:1px solid black') : () ),
% );
+%
+% if ( $f->{'m2name_table'} || $f->{'m2m_method'} ) {
+% $include[0] =~ s(^/elements/tr-)(/elements/);
+% my @label = @include;
+% $label[0] = '/elements/tr-td-label.html';
+
+ <% include( @label ) %>
+ <TD>
+ <% include( @include ) %>
+ </TD>
+
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }( %$c, 'fieldnum' => $fieldnum );
+
+ <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>'
+ style='text-align:right;vertical-align:top;
+ border-top:1px solid black;padding-top:5px;'>
+ <% $c->{'label'} || '' %>
+ </TD>
+ <TD style='border-top:1px solid black;padding-top:3px;'>
+ <% include( @column ) %>
+ </TD>
+% }
+% }
+
+ </TR>
+
+% } else {
- <% include( @include ) %>
+ <% include( @include ) %>
+% }
% if ( $f->{'m2name_table'} || $f->{'m2m_method'} ) {
<SCRIPT TYPE="text/javascript">
@@ -497,6 +579,39 @@ Example:
row.appendChild(widget_cell);
+% if ( $f->{'m2_fields'} ) {
+% foreach my $c ( @{ $f->{'m2_fields'} } ) {
+% my $column = $c->{field};
+% my @column = &{ $column_sub }(%$c, 'fieldnum' => 'MAGIC_NUMBER');
+
+ var column = <% include(@column, html_only=>1) |js_string %>;
+ column = column.replace( magic_regex, <%$field%>_fieldnum );
+
+ var column_label = document.createElement('TD');
+ column_label.id =
+ '<% $field %>__<% $column %>_label' + <%$field%>_fieldnum;
+
+ column_label.style.textAlign = "right";
+ column_label.style.verticalAlign = "top";
+ column_label.style.borderTop = "1px solid black";
+ column_label.style.paddingTop = "5px";
+
+ column_label.innerHTML = '<% $c->{'label'} || '' %>';
+
+ row.appendChild(column_label);
+
+ var column_widget = document.createElement('TD');
+
+ column_widget.style.borderTop = "1px solid black";
+ column_widget.style.paddingTop = "3px";
+
+ column_widget.innerHTML = column;
+
+ row.appendChild(column_widget);
+
+% }
+% }
+
% if ( $f->{'m2_new_js'} ) {
// take out items selected in previous dropdowns
var new_element = document.getElementById("<%$field%>" + <%$field%>_fieldnum );
@@ -562,7 +677,7 @@ Example:
<BR>
-<INPUT TYPE="submit" ID="submit" VALUE="<% ( !$clone && $object->$pkey() ) ? "Apply changes" : "Add $opt{'name'}" %>">
+<INPUT TYPE="submit" ID="submit" VALUE="<% ( !$clone && $object->$pkey() ) ? "Apply changes" : "Add ". ( $opt{'name'} || $opt{'name_singular'} ) %>">
</FORM>
@@ -618,7 +733,7 @@ if ( $cgi->param('error') ) {
map { $_ => scalar($cgi->param($_)) } fields($table)
});
- &{$opt{'error_callback'}}($cgi, $object, $fields, \%opt )
+ &{$opt{'error_callback'}}( $cgi, $object, $fields, \%opt )
if $opt{'error_callback'};
} elsif ( $cgi->param('clone') =~ /^(\d+)$/ ) {
@@ -630,9 +745,10 @@ if ( $cgi->param('error') ) {
$qsearch{'extra_sql'} = ' AND '. $opt{'agent_clone_extra_sql'}
if $opt{'agent_clone_extra_sql'};
- $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } });
+ $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } })
+ or die "$pkey $clone not found in $table";
- &{$opt{'clone_callback'}}($cgi, $object, $fields, \%opt )
+ &{$opt{'clone_callback'}}( $cgi, $object, $fields, \%opt )
if $opt{'clone_callback'};
#$object->$pkey('');
@@ -657,7 +773,7 @@ if ( $cgi->param('error') ) {
warn "$table $pkey => $1"
if $opt{'debug'};
- &{$opt{'edit_callback'}}($cgi, $object, $fields)
+ &{$opt{'edit_callback'}}( $cgi, $object, $fields, \%opt )
if $opt{'edit_callback'};
} else { #adding
@@ -672,7 +788,7 @@ if ( $cgi->param('error') ) {
? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt )
: $class->new( $hashref );
- &{$opt{'new_callback'}}($cgi, $object, $fields)
+ &{$opt{'new_callback'}}( $cgi, $object, $fields, \%opt )
if $opt{'new_callback'};
}
@@ -682,7 +798,7 @@ if ( $cgi->param('error') ) {
$opt{action} ||= $object->$pkey() ? 'Edit' : 'Add';
-my $title = $opt{action}. ' '. $opt{name};
+my $title = $opt{action}. ' '. ( $opt{name} || $opt{'name_singular'} );
my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index 0b64120..ef04bd0 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -3,7 +3,7 @@
'menubar' => [],
'error_callback' => sub {
- my( $cgi, $svc_x ) = @_;
+ my( $cgi, $svc_x, $fields, $opt ) = @_;
#$svcnum = $svc_x->svcnum;
$pkgnum = $cgi->param('pkgnum');
$svcpart = $cgi->param('svcpart');
@@ -11,11 +11,13 @@
$part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
die "No part_svc entry!" unless $part_svc;
+ label_fixup($part_svc, $opt);
+
$svc_x->setfield('svcpart', $svcpart);
},
'edit_callback' => sub {
- my( $cgi, $svc_x ) = @_;
+ my( $cgi, $svc_x, $fields, $opt ) = @_;
#$svcnum = $svc_x->svcnum;
my $cust_svc = $svc_x->cust_svc
or die "Unknown (cust_svc) svcnum!";
@@ -25,6 +27,8 @@
$part_svc = qsearchs ('part_svc', { svcpart=>$svcpart });
die "No part_svc entry!" unless $part_svc;
+
+ label_fixup($part_svc, $opt);
},
'new_hashref_callback' => sub {
@@ -35,11 +39,13 @@
},
'new_callback' => sub {
- my( $cgi, $svc_x ) = @_;;
+ my( $cgi, $svc_x, $fields, $opt ) = @_;;
$part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
die "No part_svc entry!" unless $part_svc;
+ label_fixup($part_svc, $opt);
+
#$svcnum='';
$svc_x->set_default_and_fixed;
@@ -100,6 +106,22 @@
%opt #pass through/override params
)
%>
+<%once>
+
+sub label_fixup {
+ my( $part_svc, $opt ) = @_;
+
+ #false laziness w/view/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ my $labels = $opt->{labels}; # with -> here
+ foreach my $field ( keys %$labels ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\S*$/;
+ }
+
+}
+
+</%once>
<%init>
my %opt = @_;
diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi
index 3b51141..c0ff386 100755
--- a/httemplate/edit/part_bill_event.cgi
+++ b/httemplate/edit/part_bill_event.cgi
@@ -78,7 +78,7 @@ Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
% join("\n", map {
% '<OPTION VALUE="'. $_->pkgpart. '"'.
% ( $selected{$_->pkgpart} ? ' SELECTED' : '' ).
-% '>'. $_->pkg. ' - '. $_->comment
+% '>'. $_->pkg_comment
% } qsearch('part_pkg', { 'disabled' => '' } ) ).
% '</SELECT>';
%}
@@ -178,7 +178,7 @@ Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
% 'cancel' => {
% 'name' => 'Cancel',
% 'code' => '$cust_main->cancel(reason => %%%creason%%%);',
-% 'weight' => 10,
+% 'weight' => 80, #10,
% 'reason' => 'C',
% },
%
@@ -191,7 +191,7 @@ Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
% 'comp' => {
% 'name' => 'Pay invoice with a complimentary "payment"',
% 'code' => '$cust_bill->comp();',
-% 'weight' => 30,
+% 'weight' => 90, #30,
% },
%
% 'credit' => {
diff --git a/httemplate/edit/part_device.html b/httemplate/edit/part_device.html
new file mode 100644
index 0000000..4f2fe93
--- /dev/null
+++ b/httemplate/edit/part_device.html
@@ -0,0 +1,16 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Phone device type',
+ 'table' => 'part_device',
+ 'labels' => {
+ 'devicepart' => 'Part number',
+ 'devicename' => 'Device name',
+ },
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index d579797..8b697e1 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -79,13 +79,28 @@ my $widget = new HTML::Widgets::SelectLayers(
);
$html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!;
if ( $type eq 'select' ) {
- $html .= qq!<SELECT NAME="$option">!;
- foreach my $select_option ( @{$optinfo->{options}} ) {
+ my $size = defined($optinfo->{size}) ? " SIZE=" . $optinfo->{size} : '';
+ my $multi = defined($optinfo->{multi}) ? ' MULTIPLE' : '';
+ $html .= qq!<SELECT NAME="$option"$multi$size>!;
+ my @values = split '\s+', $value if $multi;
+ my @options;
+ if (defined($optinfo->{option_values})) {
+ my $valsub = $optinfo->{option_values};
+ @options = &$valsub();
+ } elsif (defined($optinfo->{options})) {
+ @options = @{$optinfo->{options}};
+ }
+ foreach my $select_option ( @options ) {
#if ( ref($select_option) ) {
#} else {
- my $selected = $select_option eq $value ? ' SELECTED' : '';
+ my $selected = ($multi ? grep {$_ eq $select_option} @values : $select_option eq $value ) ? ' SELECTED' : '';
+ my $label = $select_option;
+ if (defined($optinfo->{option_label})) {
+ my $labelsub = $optinfo->{option_label};
+ $label = &$labelsub($select_option);
+ }
$html .= qq!<OPTION VALUE="$select_option"$selected>!.
- qq!$select_option</OPTION>!;
+ qq!$label</OPTION>!;
#}
}
$html .= '</SELECT>';
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index f404699..690e884 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -37,6 +37,8 @@
'taxproduct_select'=> 'Tax products',
'plan' => 'Price plan',
'disabled' => 'Disable new orders',
+ 'setup_cost' => 'Setup cost',
+ 'recur_cost' => 'Recur cost',
'pay_weight' => 'Payment weight',
'credit_weight' => 'Credit weight',
'agentnum' => 'Agent',
@@ -44,6 +46,7 @@
'recur_fee' => 'Recurring fee',
'bill_dst_pkgpart' => 'Include line item(s) from package',
'svc_dst_pkgpart' => 'Include services of package',
+ 'report_option' => 'Report classes',
},
'fields' => [
@@ -56,6 +59,8 @@
sub { shift->param('pkgnum') },
},
+ { field=>'custom', type=>'hidden' },
+
{ type => 'columnstart' },
{ field => 'pkg',
@@ -131,10 +136,10 @@
{ field=>'promo_code', type=>'text', size=>15 },
{ type => 'tablebreak-tr-title',
- value => 'Line-item revenue recogition', #better name?
+ value => 'Cost tracking', #better name?
},
- { field=>'pay_weight', type=>'text', size=>6 },
- { field=>'credit_weight', type=>'text', size=>6 },
+ { field=>'setup_cost', type=>'money', },
+ { field=>'recur_cost', type=>'money', },
{ type => 'columnnext' },
@@ -148,10 +153,31 @@
},
},
+ { type => 'tablebreak-tr-title',
+ value => 'Line-item revenue recogition', #better name?
+ },
+ { field=>'pay_weight', type=>'text', size=>6 },
+ { field=>'credit_weight', type=>'text', size=>6 },
+
+
{ type => 'columnend' },
- { 'type' => 'tablebreak-tr-title',
- 'value' => 'Pricing add-ons',
+ { 'type' => $census ? 'tablebreak-tr-title'
+ : 'hidden',
+ 'value' => 'Optional report classes',
+ 'field' => 'census_title',
+ },
+ { 'field' => 'report_option',
+ 'type' => $census ? 'select-table' : 'hidden',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'multiple' => 1,
+ },
+
+
+ { 'type' => 'tablebreak-tr-title',
+ 'value' => 'Pricing add-ons',
+ 'colspan' => 4,
},
{ 'field' => 'bill_dst_pkgpart',
'type' => 'select-part_pkg',
@@ -160,6 +186,13 @@
'm2m_dstcol' => 'dst_pkgpart',
'm2_error_callback' =>
&{$m2_error_callback_maker}('bill'),
+ 'm2_fields' => [ { 'field' => 'hidden',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ 'curr_value' => '',
+ 'label' => 'Bundle',
+ },
+ ],
},
{ type => 'tablebreak-tr-title',
@@ -208,12 +241,12 @@ my $disabled_type = $acl_edit_either ? 'checkbox' : 'hidden';
my $agent_clone_extra_sql =
' ( '. FS::part_pkg->curuser_pkgs_sql.
- #kludge to clone custom customer packages you otherwise couldn't see
- " OR ( part_pkg.disabled = 'Y' AND part_pkg.comment LIKE '(CUSTOM)%' ) ".
+ " OR ( part_pkg.custom = 'Y' ) ".
' ) ';
my $conf = new FS::Conf;
my $taxproducts = $conf->exists('enable_taxproducts');
+my $census = scalar( qsearch( 'part_pkg_report_option', {} ) );
#XXX
# - tr-part_pkg_freq: month_increments_only (from price plans)
@@ -291,14 +324,27 @@ my $edit_callback = sub {
(@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1});
+ my @report_option = ();
foreach ($object->options) {
/^usage_taxproductnum_(\d+)$/ && ($taxproductnums{$1} = 1);
+ /^report_option_(\d+)$/ && (push @report_option, $1);
}
foreach ($object->part_pkg_taxoverride) {
$taxproductnums{$_->usage_class} = 1
if $_->usage_class;
}
+ $cgi->param('report_option', join(',', @report_option));
+ foreach my $field ( @$fields ) {
+ next unless (
+ ref($field) eq 'HASH' &&
+ $field->{field} &&
+ $field->{field} eq 'report_option'
+ );
+ #$field->{curr_value} = join(',', @report_option);
+ $field->{value} = join(',', @report_option);
+ }
+
%options = $object->options;
$object->set($_ => $object->option($_))
@@ -328,9 +374,8 @@ my $clone_callback = sub {
$opt->{action} = 'Custom';
#my $part_pkg = $clone_part_pkg->clone;
- #this is all clone did anyway
- $object->comment( '(CUSTOM) '. $object->comment )
- unless $object->comment =~ /^\(CUSTOM\) /;
+ #this is all clone does anyway
+ $object->custom('Y');
$object->disabled('Y');
@@ -348,16 +393,26 @@ my $m2_error_callback_maker = sub {
my $link_type = shift; #yay closures
return sub {
my( $cgi, $object ) = @_;
- map {
- new FS::part_pkg_link {
- 'link_type' => $link_type,
- 'src_pkgpart' => $object->pkgpart,
- 'dst_pkgpart' => $_,
- };
- }
- grep $_,
- map $cgi->param($_),
- grep /^${link_type}_dst_pkgpart(\d+)$/, $cgi->param;
+ my $num;
+ map {
+
+ if ( /^${link_type}_dst_pkgpart(\d+)$/ &&
+ ( my $dst = $cgi->param("${link_type}_dst_pkgpart$1") ) )
+ {
+
+ my $hidden = $cgi->param("${link_type}_dst_pkgpart__hidden$1")
+ || '';
+ new FS::part_pkg_link {
+ 'link_type' => $link_type,
+ 'src_pkgpart' => $object->pkgpart,
+ 'dst_pkgpart' => $dst,
+ 'hidden' => $hidden,
+ };
+ } else {
+ ();
+ }
+ }
+ $cgi->param;
};
};
diff --git a/httemplate/edit/part_pkg_report_option.html b/httemplate/edit/part_pkg_report_option.html
new file mode 100644
index 0000000..a6f8e57
--- /dev/null
+++ b/httemplate/edit/part_pkg_report_option.html
@@ -0,0 +1,23 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Package optional report class',
+ 'table' => 'part_pkg_report_option',
+ 'fields' => [
+ 'name',
+ { field=>'num', type=>'hidden' },
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'num' => 'Class number',
+ 'name' => 'Class name',
+ 'disabled' => 'Disable class',
+ },
+ 'viewall_dir' => 'browse',
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/part_pkg_taxclass.html b/httemplate/edit/part_pkg_taxclass.html
index e767057..ad03044 100644
--- a/httemplate/edit/part_pkg_taxclass.html
+++ b/httemplate/edit/part_pkg_taxclass.html
@@ -1,32 +1,23 @@
-<% include('/elements/header.html', "$action taxclass") %>
-
-<% include('/elements/error.html') %>
-
-<FORM ACTION="<% $p1 %>process/part_pkg_taxclass.html" METHOD=POST>
-
-<INPUT TYPE="hidden" NAME="taxclassnum" VALUE="">
-
-Tax class <INPUT TYPE="text" NAME="taxclass" VALUE="<% $taxclass |h %>">
-
-<BR><BR>
-<INPUT TYPE="submit" VALUE="<% $action %> taxclass">
-
-</FORM>
-
-<% include('/elements/footer.html') %>
-
+<% include('elements/edit.html',
+ 'name_singular' => 'tax class',
+ 'table' => 'part_pkg_taxclass',
+ 'labels' => {
+ 'taxclassnum' => 'Tax class',
+ 'taxclass' => 'Tax class',
+ 'disabled' => 'Disabled',
+ },
+ 'fields' => [ 'taxclass',
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ },
+ ],
+ 'viewall_dir' => 'browse',
+ )
+%>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $taxclass = '';
-if ( $cgi->param('error') ) {
- $taxclass = $cgi->param('taxclass');
-}
-
-my $action = 'Add';
-
-my $p1 = popurl(1);
-
</%init>
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index e0fb615..7970343 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -122,6 +122,7 @@ that field.
% $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>';
%
@@ -131,7 +132,14 @@ that field.
%
% #yucky kludge
% my @fields = defined( dbdef->table($layer) )
-% ? grep { $_ ne 'svcnum' } fields($layer)
+% ? grep {
+% $_ ne 'svcnum' &&
+% ( !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'; #kludge
% $part_svc->svcpart($clone) if $clone; #haha, undone below
@@ -139,13 +147,15 @@ that field.
%
% foreach my $field (@fields) {
%
-% #my $def = $defs{$layer}{$field};
+% #a few lines of false laziness w/browse/part_svc.cgi
% my $def = FS::part_svc->svc_table_fields($layer)->{$field};
-% my $label = $def->{'def_label'} || $def->{'label'};
+% 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;
+% my $flag = $part_svc_column->columnflag;
%
% if ( $bgcolor eq $bgcolor1 ) {
% $bgcolor = $bgcolor2;
@@ -153,9 +163,12 @@ that field.
% $bgcolor = $bgcolor1;
% }
%
-% $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
-% ( $label || $field ).
+% $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">!;
@@ -295,6 +308,15 @@ that field.
% }
%
% $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) {
%
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
index e3893cf..4c7bae1 100644
--- a/httemplate/edit/payment_gateway.html
+++ b/httemplate/edit/payment_gateway.html
@@ -1,132 +1,134 @@
-<% include("/elements/header.html","$action Payment gateway", menubar(
- 'View all payment gateways' => $p. 'browse/payment_gateway.html',
-)) %>
-
-<% include('/elements/error.html') %>
-
-<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST>
-<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>">
-Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %>
-
-<% ntable('#cccccc', 2, '') %>
-
-<TR>
- <TH ALIGN="right">Gateway: </TH>
- <TD>
-% if ( $payment_gateway->gatewaynum ) {
-
-
- <% $payment_gateway->gateway_module %>
- <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>">
-% } else {
-
-
- <SELECT NAME="gateway_module" SIZE=1>
-% foreach my $module ( qw(
-% 2CheckOut
-% AuthorizeNet
-% BankOfAmerica
-% Beanstream
-% Capstone
-% Cardstream
-% CashCow
-% CyberSource
-% eSec
-% eSelectPlus
-% Exact
-% iAuthorizer
-% IPaymentTPG
-% Jettis
-% LinkPoint
-% MerchantCommerce
-% Network1Financial
-% OCV
-% OpenECHO
-% PayConnect
-% PayflowPro
-% PaymentsGateway
-% PXPost
-% SecureHostingUPG
-% Skipjack
-% StGeorge
-% SurePay
-% TCLink
-% TransactionCentral
-% TransFirsteLink
-% VirtualNet
-% ) ) {
-%
-
- <OPTION VALUE="<% $module %>"><% $module %>
-% }
-
- </SELECT>
+<% include( 'elements/edit.html',
+ 'table' => 'payment_gateway',
+ 'name_singular' => 'Payment gateway',
+ 'viewall_dir' => 'browse',
+ 'fields' => $fields,
+ 'field_callback' => $field_callback,
+ 'labels' => {
+ 'gatewaynum' => 'Gateway #',
+ 'gateway_module' => 'Gateway',
+ 'gateway_username' => 'Username',
+ 'gateway_password' => 'Password',
+ 'gateway_action' => 'Action',
+ 'gateway_options' => 'Options: (Name/Value pairs, one element per line)',
+ 'gateway_callback_url' => 'Callback URL',
+ },
+ )
+%>
+
+
+<SCRIPT TYPE="text/javascript">
+ var gatewayNamespace = new Array;
+
+% foreach my $module ( sort { lc($a) cmp lc ($b) } keys %modules ) {
+ gatewayNamespace.push('<% $modules{$module} %>')
% }
+ // document.getElementById('gateway_namespace').value = gatewayNamespace[0];
+ function setNamespace(what) {
+ document.getElementById('gateway_namespace').value =
+ gatewayNamespace[what.selectedIndex];
+ }
- </TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Username: </TH>
- <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Password: </TH>
- <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Action: </TH>
- <TD>
- <SELECT NAME="gateway_action" SIZE=1>
-% foreach my $action (
-% 'Normal Authorization',
-% 'Authorization Only',
-% 'Authorization Only, Post Authorization',
-% ) {
-%
-
- <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %>
-% }
-
- </SELECT>
- </TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Options: (Name/Value pairs, one element per line)</TH>
- <TD>
- <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA>
- </TD>
-</TR>
-
-</TABLE>
-
-<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>">
- </FORM>
-
-<% include('/elements/footer.html') %>
+</SCRIPT>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $payment_gateway;
-if ( $cgi->param('error') ) {
- $payment_gateway = new FS::payment_gateway ( {
- map { $_, scalar($cgi->param($_)) } fields('payment_gateway')
- } );
-} elsif ( $cgi->keywords ) {
- my($query) = $cgi->keywords;
- $query =~ /^(\d+)$/;
- $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } );
-} else { #adding
- $payment_gateway = new FS::payment_gateway {};
-}
-my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add';
-#my $hashref = $payment_gateway->hashref;
+my %modules = (
+ '2CheckOut' => 'Business::OnlinePayment',
+ 'AuthorizeNet' => 'Business::OnlinePayment',
+ 'BankOfAmerica' => 'Business::OnlinePayment', #deprecated?
+ 'Beanstream' => 'Business::OnlinePayment',
+ 'Capstone' => 'Business::OnlinePayment',
+ 'Cardstream' => 'Business::OnlinePayment',
+ 'CashCow' => 'Business::OnlinePayment',
+ 'CyberSource' => 'Business::OnlinePayment',
+ 'eSec' => 'Business::OnlinePayment',
+ 'eSelectPlus' => 'Business::OnlinePayment',
+ 'Exact' => 'Business::OnlinePayment',
+ 'iAuthorizer' => 'Business::OnlinePayment',
+ 'Ingotz' => 'Business::OnlinePayment',
+ 'InternetSecure' => 'Business::OnlinePayment',
+ 'Interswitchng' => 'Business::OnlineThirdPartyPayment',
+ 'IPaymentTPG' => 'Business::OnlinePayment',
+ 'IPPay' => 'Business::OnlinePayment',
+ 'Iridium' => 'Business::OnlinePayment',
+ 'Jettis' => 'Business::OnlinePayment',
+ 'LinkPoint' => 'Business::OnlinePayment',
+ 'MerchantCommerce' => 'Business::OnlinePayment',
+ 'Network1Financial' => 'Business::OnlinePayment',
+ 'OCV' => 'Business::OnlinePayment',
+ 'OpenECHO' => 'Business::OnlinePayment',
+ 'PayConnect' => 'Business::OnlinePayment',
+ 'PayflowPro' => 'Business::OnlinePayment',
+ 'PaymenTech' => 'Business::OnlinePayment',
+ 'PaymentsGateway' => 'Business::OnlinePayment',
+ 'PayPal' => 'Business::OnlinePayment',
+ 'PlugnPay ' => 'Business::OnlinePayment',
+ 'PPIPayMover ' => 'Business::OnlinePayment',
+ 'Protx ' => 'Business::OnlinePayment',
+ 'PXPost' => 'Business::OnlinePayment',
+ 'SecureHostingUPG' => 'Business::OnlinePayment',
+ 'Skipjack' => 'Business::OnlinePayment',
+ 'StGeorge' => 'Business::OnlinePayment',
+ 'SurePay' => 'Business::OnlinePayment',
+ 'TCLink' => 'Business::OnlinePayment',
+ 'TransactionCentral' => 'Business::OnlinePayment',
+ 'TransFirsteLink' => 'Business::OnlinePayment',
+ 'Vanco' => 'Business::OnlinePayment',
+ 'viaKLIX' => 'Business::OnlinePayment',
+ 'VirtualNet' => 'Business::OnlinePayment',
+ 'WesternACH' => 'Business::OnlinePayment',
+);
+
+my @actions = (
+ 'Normal Authorization',
+ 'Authorization Only',
+ 'Authorization Only, Post Authorization',
+ );
+
+my $fields = [
+ {
+ field => 'gateway_namespace',
+ type => 'hidden',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ $modules{$object->gateway_module}
+ || 'Business::OnlinePayment'
+ },
+ },
+ {
+ field => 'gateway_module',
+ type => 'select',
+ options => [ sort { lc($a) cmp lc ($b) } keys %modules ],
+ onchange => 'setNamespace',
+ },
+ 'gateway_username',
+ 'gateway_password',
+ {
+ field => 'gateway_action',
+ type => 'select',
+ options => \@actions,
+ },
+ 'gateway_callback_url',
+ {
+ field => 'gateway_options',
+ type => 'textarea',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ join("\r", $object->options );
+ },
+ },
+ ];
+
+my $field_callback = sub {
+ my ($cgi, $object, $field_hashref ) = @_;
+ if ($object->gatewaynum) {
+ if ( $field_hashref->{field} eq 'gateway_module' ) {
+ $field_hashref->{type} = 'fixed';
+ }
+ }
+};
</%init>
diff --git a/httemplate/edit/phone_device.html b/httemplate/edit/phone_device.html
new file mode 100644
index 0000000..a1aa166
--- /dev/null
+++ b/httemplate/edit/phone_device.html
@@ -0,0 +1,37 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Phone device',
+ 'table' => 'phone_device',
+ 'labels' => {
+ 'devicenum' => 'Device',
+ 'devicepart' => 'Device type',
+ 'mac_addr' => 'MAC address',
+ },
+ 'fields' => [ { 'field' => 'devicepart',
+ 'type' => 'select-table',
+ 'table' => 'part_device',
+ 'name_col' => 'devicename',
+ 'empty_label' =>'Select device type',
+ #'hashref' =>{ disabled => '' },
+ },
+ 'mac_addr',
+ { 'field' => 'svcnum',
+ 'type' => 'hidden',
+ },
+ ],
+ 'menubar' => [], #disable viewall
+ #'viewall_dir' => 'browse',
+ 'new_callback' => sub {
+ my( $cgi, $object ) = @_;
+ $object->svcnum( $cgi->param('svcnum') );
+ },
+ )
+%>
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+
+</%init>
diff --git a/httemplate/edit/pkg_category.html b/httemplate/edit/pkg_category.html
index fdc8da6..a07dc58 100644
--- a/httemplate/edit/pkg_category.html
+++ b/httemplate/edit/pkg_category.html
@@ -3,11 +3,13 @@
'table' => 'pkg_category',
'fields' => [
'categoryname',
+ 'weight',
{ field=>'disabled', type=>'checkbox', value=>'Y', },
],
'labels' => {
'categorynum' => 'Category number',
'categoryname' => 'Category name',
+ 'weight' => 'Weight',
'disabled' => 'Disable category',
},
'viewall_dir' => 'browse',
diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi
index 9e1c30b..ed404b7 100644
--- a/httemplate/edit/prepay_credit.cgi
+++ b/httemplate/edit/prepay_credit.cgi
@@ -97,14 +97,14 @@ tie my %multiplier, 'Tie::IxHash',
tie my %bytemultiplier, 'Tie::IxHash',
1 => 'bytes',
- 1000 => 'Kbytes',
- 1000000 => 'Mbytes',
- 1000000000 => 'Gbytes',
+ 1024 => 'Kbytes',
+ 1048576 => 'Mbytes',
+ 1073741824 => 'Gbytes',
;
$cgi->param('multiplier', '60') unless $cgi->param('multiplier');
-$cgi->param('upmultiplier', '1000000') unless $cgi->param('upmultiplier');
-$cgi->param('downmultiplier', '1000000') unless $cgi->param('downmultiplier');
-$cgi->param('totalmultiplier','1000000') unless $cgi->param('totalmultiplier');
+$cgi->param('upmultiplier', '1048576') unless $cgi->param('upmultiplier');
+$cgi->param('downmultiplier', '1048576') unless $cgi->param('downmultiplier');
+$cgi->param('totalmultiplier','1048576') unless $cgi->param('totalmultiplier');
</%init>
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
index ebcb7e4..d4ba976 100755
--- a/httemplate/edit/process/REAL_cust_pkg.cgi
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -3,16 +3,23 @@
<% $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ) %>
%} else {
% my $custnum = $new->custnum;
-<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum#cust_pkg$pkgnum" ) %>
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" ) %>
%}
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit customer package dates');
+ unless $curuser->access_right('Edit customer package dates');
my $pkgnum = $cgi->param('pkgnum') or die;
my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
my %hash = $old->hash;
+$hash{'start_date'} = $cgi->param('start_date') ? str2time($cgi->param('start_date')) : '';
$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : '';
$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : '';
$hash{'last_bill'} =
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 097d382..f72ca0a 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -19,6 +19,8 @@ my $DEBUG = 0;
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+my $conf = new FS::Conf;
+
my $error = '';
#unmunge stuff
@@ -27,8 +29,7 @@ $cgi->param('tax','') unless defined $cgi->param('tax');
$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
-#my $payby = $cgi->param('payby');
-my $payby = $cgi->param('select'); # XXX key
+my $payby = $cgi->param('payby');
my %noauto = (
'CARD' => 'DCRD',
@@ -72,55 +73,83 @@ if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) {
);
}
-if ( $cgi->param('birthdate') && $cgi->param('birthdate') =~ /^([ 0-9\-\/]{0,10})$/) {
- my $conf = new FS::Conf;
- my $format = $conf->config('date_format') || "%m/%d/%Y";
- my $parser = DateTime::Format::Strptime->new(pattern => $format,
- time_zone => 'floating',
- );
- my $dt = $parser->parse_datetime($1);
- if ($dt) {
- $new->setfield('birthdate', $dt->epoch);
- $cgi->param('birthdate', $dt->epoch);
- } else {
-# $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg;
- $error ||= "Invalid birthdate: " . $cgi->param('birthdate') . ".";
- $cgi->param('birthdate', '');
+my %usedatetime = ( 'birthdate' => 1 );
+
+foreach my $dfield (qw( birthdate signupdate )) {
+
+ if ( $cgi->param($dfield) && $cgi->param($dfield) =~ /^([ 0-9\-\/]{0,10})$/) {
+
+ my $value = $1;
+ my $parsed = '';
+
+ if ( exists $usedatetime{$dfield} && $usedatetime{$dfield} ) {
+
+ my $format = $conf->config('date_format') || "%m/%d/%Y";
+ my $parser = DateTime::Format::Strptime->new( pattern => $format,
+ time_zone => 'floating',
+ );
+ my $dt = $parser->parse_datetime($value);
+ if ( $dt ) {
+ $parsed = $dt->epoch;
+ } else {
+ # $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg;
+ $error ||= "Invalid $dfield: $value";
+ }
+
+ } else {
+
+ $parsed = str2time($value)
+ or $error ||= "Invalid $dfield: $value";
+
+ }
+
+ $new->setfield( $dfield, $parsed );
+ $cgi->param( $dfield, $parsed );
+
}
+
}
$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;
+
#perhaps this stuff should go to cust_main.pm
-my $cust_pkg = '';
-my $svc_acct = '';
if ( $new->custnum eq '' ) {
+ my $cust_pkg = '';
+ my $svc;
+
if ( $cgi->param('pkgpart_svcpart') ) {
+
my $x = $cgi->param('pkgpart_svcpart');
$x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n";
my($pkgpart, $svcpart) = ($1, $2);
+ my $part_pkg = qsearchs('part_pkg', { 'pkgpart' => $pkgpart } );
#false laziness: copied from FS::cust_pkg::order (which should become a
#FS::cust_main method)
my(%part_pkg);
# generate %part_pkg
# $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart
my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum });
- #my($type_pkgs);
- #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
- # my($pkgpart)=$type_pkgs->pkgpart;
- # $part_pkg{$pkgpart}++;
- #}
- # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
- my $pkgpart_href = $agent->pkgpart_hashref;
- #eslaf
-
- # this should wind up in FS::cust_pkg!
- $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ".
- "purchase pkgpart ". $pkgpart
- #unless $part_pkg{ $pkgpart };
- unless $pkgpart_href->{ $pkgpart };
+
+ if ( $agent ) {
+ # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
+ my $pkgpart_href = $agent->pkgpart_hashref
+ if $agent;
+ #eslaf
+
+ # this should wind up in FS::cust_pkg!
+ $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum.
+ ") can't purchase pkgpart ". $pkgpart
+ #unless $part_pkg{ $pkgpart };
+ unless $pkgpart_href->{ $pkgpart }
+ || $agent->agentnum == $part_pkg->agentnum;
+ } else {
+ $error = 'Select agent';
+ }
$cust_pkg = new FS::cust_pkg ( {
#later 'custnum' => $custnum,
@@ -132,33 +161,52 @@ if ( $new->custnum eq '' ) {
#$error ||= $cust_svc->check;
- my %svc_acct = (
- 'svcpart' => $svcpart,
- 'username' => $cgi->param('username'),
- '_password' => $cgi->param('_password'),
- 'popnum' => $cgi->param('popnum'),
- );
- $svc_acct{'domsvc'} = $cgi->param('domsvc')
- if $cgi->param('domsvc');
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ my $svcdb = $part_svc->svcdb;
+
+ if ( $svcdb eq 'svc_acct' ) {
+
+ my %svc_acct = (
+ 'svcpart' => $svcpart,
+ 'username' => scalar($cgi->param('username')),
+ '_password' => scalar($cgi->param('_password')),
+ 'popnum' => scalar($cgi->param('popnum')),
+ );
+ $svc_acct{'domsvc'} = $cgi->param('domsvc')
+ if $cgi->param('domsvc');
+
+ $svc = new FS::svc_acct \%svc_acct;
+
+ #and just in case you were silly
+ $svc->svcpart($svcpart);
+ $svc->username($cgi->param('username'));
+ $svc->_password($cgi->param('_password'));
+ $svc->popnum($cgi->param('popnum'));
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+
+ my %svc_phone = (
+ 'svcpart' => $svcpart,
+ map { $_ => scalar($cgi->param($_)) }
+ qw( countrycode phonenum sip_password pin phone_name )
+ );
- $svc_acct = new FS::svc_acct \%svc_acct;
+ $svc = new FS::svc_phone \%svc_phone;
- #and just in case you were silly
- $svc_acct->svcpart($svcpart);
- $svc_acct->username($cgi->param('username'));
- $svc_acct->_password($cgi->param('_password'));
- $svc_acct->popnum($cgi->param('popnum'));
+ } else {
+ die "$svcdb not handled on new customer yet";
+ }
#$error ||= $svc_acct->check;
- } elsif ( $cgi->param('username') ) { #good thing to catch
- $error = "Can't assign username without a package!";
}
use Tie::RefHash;
tie my %hash, 'Tie::RefHash';
- %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg;
- $error ||= $new->insert( \%hash, \@invoicing_list );
+ %hash = ( $cust_pkg => [ $svc ] ) if $cust_pkg;
+ $error ||= $new->insert( \%hash, \@invoicing_list,
+ 'tax_exemption' => \@tax_exempt,
+ );
my $conf = new FS::Conf;
if ( $conf->exists('backend-realtime') && ! $error ) {
@@ -201,7 +249,9 @@ if ( $new->custnum eq '' ) {
local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG;
local($FS::Record::DEBUG) = $DEBUG if $DEBUG;
- $error ||= $new->replace($old, \@invoicing_list);
+ $error ||= $new->replace( $old, \@invoicing_list,
+ 'tax_exemption' => \@tax_exempt,
+ );
warn "$me returned from replace" if $DEBUG;
diff --git a/httemplate/edit/process/cust_main_attach.cgi b/httemplate/edit/process/cust_main_attach.cgi
new file mode 100644
index 0000000..98f4d09
--- /dev/null
+++ b/httemplate/edit/process/cust_main_attach.cgi
@@ -0,0 +1,99 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_main_attach.cgi?'. $cgi->query_string ) %>
+%} else {
+% my $act = 'added';
+% $act = 'updated' if ($attachnum);
+% $act = 'purged' if($attachnum and $purge);
+% $act = 'undeleted' if($attachnum and $undelete);
+% $act = 'deleted' if($attachnum and $delete);
+<% header('Attachment ' . $act ) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+my $error;
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die "Illegal custnum: ". $cgi->param('custnum');
+my $custnum = $1;
+
+$cgi->param('attachnum') =~ /^(\d*)$/
+ or die "Illegal attachnum: ". $cgi->param('attachnum');
+my $attachnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $otaker = $curuser->name;
+$otaker = $curuser->username if ($otaker eq "User, Legacy");
+
+my $delete = $cgi->param('delete');
+my $undelete = $cgi->param('undelete');
+my $purge = $cgi->param('purge');
+
+my $new = new FS::cust_attachment ( {
+ attachnum => $attachnum,
+ custnum => $custnum,
+ _date => time,
+ otaker => $otaker,
+ disabled => '',
+});
+my $old;
+
+if($attachnum) {
+ $old = qsearchs('cust_attachment', { attachnum => $attachnum });
+ if(!$old) {
+ $error = "Attachnum '$attachnum' not found";
+ }
+ elsif($purge) { # do nothing
+ }
+ else {
+ map { $new->$_($old->$_) }
+ ('_date', 'otaker', 'body', 'disabled');
+ $new->filename($cgi->param('filename') || $old->filename);
+ $new->mime_type($cgi->param('mime_type') || $old->mime_type);
+ if($delete and not $old->disabled) {
+ $new->disabled(time);
+ }
+ if($undelete and $old->disabled) {
+ $new->disabled('');
+ }
+ }
+}
+else { # This is a new attachment, so require a file.
+
+ my $filename = $cgi->param('file');
+ if($filename) {
+ $new->filename($filename);
+ $new->mime_type($cgi->uploadInfo($filename)->{'Content-Type'});
+
+ local $/;
+ my $fh = $cgi->upload('file');
+ $new->body(<$fh>);
+ }
+ else {
+ $error = 'No file uploaded';
+ }
+}
+my $action = 'Add';
+$action = 'Edit' if $attachnum;
+$action = 'Delete' if $attachnum and $delete;
+$action = 'Undelete' if $attachnum and $undelete;
+$action = 'Purge' if $attachnum and $purge;
+
+$error = 'access denied' unless $curuser->access_right($action . ' attachment');
+
+if(!$error) {
+ if($old and $old->disabled and $purge) {
+ $error = $old->delete;
+ }
+ elsif($old) {
+ $error = $new->replace($old);
+ }
+ else {
+ $error = $new->insert;
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi
index a917825..18bd1fd 100755
--- a/httemplate/edit/process/cust_main_county-collapse.cgi
+++ b/httemplate/edit/process/cust_main_county-collapse.cgi
@@ -1,44 +1,37 @@
-%
-%
-%my($query) = $cgi->keywords;
-%$query =~ /^(\d+)$/ or die "Illegal taxnum!";
-%my $taxnum = $1;
-%my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
-% or die "Unknown taxnum $taxnum";
-%
-%#really should do this in a .pm & start transaction
-%
-%foreach my $delete ( qsearch('cust_main_county', {
-% 'country' => $cust_main_county->country,
-% 'state' => $cust_main_county->state
-% } ) ) {
-%# unless ( qsearch('cust_main',{
-%# 'state' => $cust_main_county->getfield('state'),
-%# 'county' => $cust_main_county->getfield('county'),
-%# 'country' => $cust_main_county->getfield('country'),
-%# } ) ) {
-% my $error = $delete->delete;
-% die $error if $error;
-%# } else {
-% #should really fix the $cust_main record
-%# }
-%
-%}
-%
-%$cust_main_county->taxnum('');
-%$cust_main_county->county('');
-%my $error = $cust_main_county->insert;
-%die $error if $error;
-%
-%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
-%
-%
+<% $cgi->redirect(popurl(3). "browse/cust_main_county.cgi") %>
<%init>
-#this isn't actually linked from anywhere just now, but it will be again soon
-
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ or die "Illegal taxnum!";
+my $taxnum = $1;
+my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+ or die "Unknown taxnum $taxnum";
+
+#really should do this in a .pm & start transaction
+
+foreach my $delete ( qsearch('cust_main_county', {
+ 'country' => $cust_main_county->country,
+ 'state' => $cust_main_county->state
+ } ) ) {
+# unless ( qsearch('cust_main',{
+# 'state' => $cust_main_county->getfield('state'),
+# 'county' => $cust_main_county->getfield('county'),
+# 'country' => $cust_main_county->getfield('country'),
+# } ) ) {
+ my $error = $delete->delete;
+ die $error if $error;
+# } else {
+ #should really fix the $cust_main record
+# }
+
+}
+
+$cust_main_county->taxnum('');
+$cust_main_county->county('');
+my $error = $cust_main_county->insert;
+die $error if $error;
</%init>
diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi
index 647f6fc..a310c53 100755
--- a/httemplate/edit/process/cust_pay.cgi
+++ b/httemplate/edit/process/cust_pay.cgi
@@ -7,7 +7,7 @@
% if ( $cgi->param('apply') eq 'yes' ) {
% my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum })
% or die "unknown custnum $linknum";
-% $cust_main->apply_payments;
+% $cust_main->apply_payments( 'manual' => 1 );
% }
% if ( $link eq 'popup' ) {
%
@@ -46,7 +46,9 @@ my $new = new FS::cust_pay ( {
_date => $_date,
map {
$_, scalar($cgi->param($_));
- } qw(paid payby payinfo paybatch)
+ } qw( paid payby payinfo paybatch
+ pkgnum
+ )
#} fields('cust_pay')
} );
diff --git a/httemplate/edit/process/cust_tax_adjustment.html b/httemplate/edit/process/cust_tax_adjustment.html
new file mode 100644
index 0000000..204b5b9
--- /dev/null
+++ b/httemplate/edit/process/cust_tax_adjustment.html
@@ -0,0 +1,41 @@
+% if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect($p.'cust_tax_adjustment.html?'. $cgi->query_string) %>
+% } else {
+<% header("Tax adjustment added") %>
+ <SCRIPT TYPE="text/javascript">
+ //window.top.location.reload();
+ parent.cClick();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $error = '';
+my $conf = new FS::conf;
+my $param = $cgi->Vars;
+
+$param->{"custnum"} =~ /^(\d+)$/
+ or $error .= "Illegal customer number " . $param->{"custnum"} . " ";
+my $custnum = $1;
+
+$param->{"amount"} =~ /^\s*(\d*(?:\.?\d{1,2}))\s*$/
+ or $error .= "Illegal amount " . $param->{"amount"} . " ";
+my $amount = $1;
+
+unless ( $error ) {
+
+ my $cust_tax_adjustment = new FS::cust_tax_adjustment {
+ 'custnum' => $custnum,
+ 'taxname' => $param->{'taxname'},
+ 'amount' => $amount,
+ 'comment' => $param->{'comment'},
+ };
+ $error = $cust_tax_adjustment->insert;
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/domreg.cgi b/httemplate/edit/process/domreg.cgi
new file mode 100755
index 0000000..a95474e
--- /dev/null
+++ b/httemplate/edit/process/domreg.cgi
@@ -0,0 +1,62 @@
+%if ($error) {
+% $cgi->param('error', $error);
+% errorpage($error);
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('op') =~ /^(register|transfer|revoke|renew)$/ or die "Illegal operation";
+my $operation = $1;
+#my($query) = $cgi->keywords;
+#$query =~ /^(\d+)$/;
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+my $svc_domain = qsearchs({
+ 'select' => 'svc_domain.*',
+ 'table' => 'svc_domain',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Unknown svcnum" unless $svc_domain;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $error = '';
+
+my @exports = $part_svc->part_export();
+
+my $registrar;
+my $export;
+
+# Find the first export that does domain registration
+foreach (@exports) {
+ $export = $_ if $_->can('registrar');
+}
+
+my $period = 1; # Current OpenSRS export can only handle 1 year registrations
+
+# If we have a domain registration export, get the registrar object
+if ($export) {
+ if ($operation eq 'register') {
+ $error = $export->register( $svc_domain, $period );
+ } elsif ($operation eq 'transfer') {
+ $error = $export->transfer( $svc_domain );
+ } elsif ($operation eq 'revoke') {
+ $error = $export->revoke( $svc_domain );
+ } elsif ($operation eq 'renew') {
+ $cgi->param('period') =~ /^(\d+)$/ or die "Illegal renewal period!";
+ $period = $1;
+ $error = $export->renew( $svc_domain, $period );
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/ApplicationCommon.html b/httemplate/edit/process/elements/ApplicationCommon.html
index 2782dc2..e0c5bd7 100644
--- a/httemplate/edit/process/elements/ApplicationCommon.html
+++ b/httemplate/edit/process/elements/ApplicationCommon.html
@@ -72,6 +72,6 @@ my $new;
#}
-my $error = $new->insert;
+my $error = $new->insert( 'manual' => 1 );
</%init>
diff --git a/httemplate/edit/process/part_device.html b/httemplate/edit/process/part_device.html
new file mode 100644
index 0000000..2b7e1da
--- /dev/null
+++ b/httemplate/edit/process/part_device.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_device',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
index b5f82e8..209419f 100644
--- a/httemplate/edit/process/part_export.cgi
+++ b/httemplate/edit/process/part_export.cgi
@@ -16,7 +16,8 @@ my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum;
#fixup options
#warn join('-', split(',',$cgi->param('options')));
my %options = map {
- my $value = $cgi->param($_);
+ my @values = $cgi->param($_);
+ my $value = scalar(@values) > 1 ? join (' ', @values) : $values[0];
$value =~ s/\r\n/\n/g; #browsers? (textarea)
$_ => $value;
} split(',', $cgi->param('options'));
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index 96c5b36..019224c 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -93,6 +93,7 @@ my $args_callback = sub {
}
( $optionname => $value );
}
+ grep { $_ !~ /^report_option_/ }
@options;
foreach ( split(',', $cgi->param('taxproductnums') ) ) {
@@ -102,6 +103,11 @@ my $args_callback = sub {
$options{"usage_taxproductnum_$_"} = $value;
}
+ foreach ( $cgi->param('report_option') ) {
+ $error ||= "Illegal optional report class: $_" unless ( $_ =~ /^\d*$/ );
+ $options{"report_option_$_"} = 1;
+ }
+
$options{$_} = scalar( $cgi->param($_) )
for (qw( setup_fee recur_fee ));
@@ -134,7 +140,13 @@ my $args_callback = sub {
my $redirect_callback = sub {
#my( $cgi, $new ) = @_;
return '' unless $custnum;
- popurl(3). "view/cust_main.cgi?keywords=$custnum;dummy=";
+ my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+ #my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+
+ #can we link back to the specific customized package? it would be nice...
+ popurl(3). "view/cust_main.cgi?custnum=$custnum$show;dummy=";
};
#these should probably move to @args above and be processed by part_pkg.pm...
@@ -152,16 +164,28 @@ my @process_m2m = (
'target_table' => 'part_pkg',
'base_field' => 'src_pkgpart',
'target_field' => 'dst_pkgpart',
- 'hashref' => { 'link_type' => 'bill' },
- 'params' => [ map $cgi->param($_), grep /^bill_dst_pkgpart/, $cgi->param ],
- },
- { 'link_table' => 'part_pkg_link',
- 'target_table' => 'part_pkg',
- 'base_field' => 'src_pkgpart',
- 'target_field' => 'dst_pkgpart',
- 'hashref' => { 'link_type' => 'svc' },
- 'params' => [ map $cgi->param($_), grep /^svc_dst_pkgpart/, $cgi->param ],
+ 'hashref' => { 'link_type' => 'svc', 'hidden' => '' },
+ 'params' => [ map $cgi->param($_),
+ grep /^svc_dst_pkgpart/, $cgi->param
+ ],
},
+ map {
+ my $hidden = $_;
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'bill', 'hidden' => $hidden },
+ 'params' => [ map { $cgi->param($_) }
+ grep { my $param = "bill_dst_pkgpart__hidden";
+ my $digit = '';
+ (($digit) = /^bill_dst_pkgpart(\d+)/ ) &&
+ $cgi->param("$param$digit") eq $hidden;
+ }
+ $cgi->param
+ ],
+ },
+ } ( '', 'Y' ),
);
foreach my $override_class ($cgi->param) {
diff --git a/httemplate/edit/process/part_pkg_report_option.html b/httemplate/edit/process/part_pkg_report_option.html
new file mode 100644
index 0000000..052aabd
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_report_option.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_pkg_report_option',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg_taxclass.html b/httemplate/edit/process/part_pkg_taxclass.html
index 8f149bb..b37279f 100644
--- a/httemplate/edit/process/part_pkg_taxclass.html
+++ b/httemplate/edit/process/part_pkg_taxclass.html
@@ -1,53 +1,17 @@
-% if ( $error ) {
-% $cgi->param('error', $error);
-<% $cgi->redirect(popurl(2). "part_pkg_taxclass.html?". $cgi->query_string ) %>
-%} else {
-<% $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?taxclass=". uri_escape($part_pkg_taxclass->taxclass) ) %>
-%}
+<% include( 'elements/process.html',
+ 'table' => 'part_pkg_taxclass',
+ 'redirect' => sub {
+ my( $cgi, $part_pkg_taxclass ) = @_;
+
+ popurl(3). 'browse/cust_main_county.cgi?'.
+ 'taxclass='. uri_escape($part_pkg_taxclass->taxclass).
+ ';dummy=';
+ },
+ )
+%>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $part_pkg_taxclass = new FS::part_pkg_taxclass {
- 'taxclass' => $cgi->param('taxclass'),
-};
-
-#maybe this whole thing should be in a transaction. at some point, no biggie
-#none of the follow-up stuff will fail unless there's a more serious problem
-#than a hanging record in part_pkg_taxclass...
-
-my $error = $part_pkg_taxclass->insert;
-
-unless ( $error ) {
- #auto-add the new taxclass to any regions that have taxclasses already
-
- my $sth = dbh->prepare("
- SELECT country, state, county FROM cust_main_county
- WHERE taxclass IS NOT NULL AND taxclass != ''
- GROUP BY country, state, county
- ") or die dbh->errstr;
- $sth->execute or die $sth->errstr;
-
- while ( my $row = $sth->fetchrow_hashref ) {
- warn "inserting for $row";
- my $cust_main_county = new FS::cust_main_county {
- 'country' => $row->{country},
- 'state' => $row->{state},
- 'county' => $row->{county},
- 'tax' => 0,
- 'taxclass' => $part_pkg_taxclass->taxclass,
- #exempt_amount
- #taxname
- #setuptax
- #recurtax
- };
- $error = $cust_main_county->insert;
- #last if $error;
- die $error if $error;
- }
-
-
-}
-
</%init>
diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html
index b16bc3d..812c988 100644
--- a/httemplate/edit/process/payment_gateway.html
+++ b/httemplate/edit/process/payment_gateway.html
@@ -1,35 +1,22 @@
-%if ( $error ) {
-% $cgi->param('error', $error);
-<% $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ) %>
-%} else {
-<% $cgi->redirect(popurl(3). "browse/payment_gateway.html") %>
-%}
+<% include( 'elements/process.html',
+ 'table' => 'payment_gateway',
+ 'viewall_dir' => 'browse',
+ 'args_callback' => $args_callback,
+ )
+%>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $gatewaynum = $cgi->param('gatewaynum');
+my $args_callback = sub {
+ my ( $cgi, $new ) = @_;
-my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum;
+ my @options = split(/\r?\n/, $cgi->param('gateway_options') );
+ pop @options
+ if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+ (@options)
+};
-my $new = new FS::payment_gateway ( {
- map {
- $_, scalar($cgi->param($_));
- } fields('payment_gateway')
-} );
-
-my @options = split(/\r?\n/, $cgi->param('gateway_options') );
-pop @options
- if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
-my %options = @options;
-
-my $error;
-if ( $gatewaynum ) {
- $error=$new->replace($old, \%options);
-} else {
- $error=$new->insert(\%options);
- $gatewaynum=$new->getfield('gatewaynum');
-}
</%init>
diff --git a/httemplate/edit/process/phone_device.html b/httemplate/edit/process/phone_device.html
new file mode 100644
index 0000000..df9d5e7
--- /dev/null
+++ b/httemplate/edit/process/phone_device.html
@@ -0,0 +1,18 @@
+<% include( 'elements/process.html',
+ 'table' => 'phone_device',
+ 'redirect' => sub {
+ my( $cgi, $phone_device ) = @_;
+ popurl(3).'view/svc_phone.cgi?'.
+ 'svcnum='. $phone_device->svcnum.
+ ';devicenum=';
+ },
+ )
+%>
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi
index 8fa57dd..827530e 100644
--- a/httemplate/edit/process/quick-charge.cgi
+++ b/httemplate/edit/process/quick-charge.cgi
@@ -27,7 +27,7 @@ $param->{"custnum"} =~ /^(\d+)$/
or $error .= "Illegal customer number " . $param->{"custnum"} . " ";
my $custnum = $1;
-$param->{"amount"} =~ /^\s*(\d+(\.\d{1,2})?)\s*$/
+$param->{"amount"} =~ /^\s*(\d*(?:\.?\d{1,2}))\s*$/
or $error .= "Illegal amount " . $param->{"amount"} . " ";
my $amount = $1;
@@ -55,6 +55,12 @@ unless ( $error ) {
$error ||= $cust_main->charge( {
'amount' => $amount,
'quantity' => $quantity,
+ 'bill_now' => scalar($cgi->param('bill_now')),
+ 'invoice_terms' => scalar($cgi->param('invoice_terms')),
+ 'start_date' => ( scalar($cgi->param('start_date'))
+ ? str2time($cgi->param('start_date'))
+ : ''
+ ),
'pkg' => scalar($cgi->param('pkg')),
'setuptax' => scalar($cgi->param('setuptax')),
'taxclass' => scalar($cgi->param('taxclass')),
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index 9c24743..7a0f082 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -3,12 +3,15 @@
<% $cgi->redirect(popurl(3). 'misc/order_pkg.html?'. $cgi->query_string ) %>
%} else {
% my $frag = "cust_pkg". $cust_pkg->pkgnum;
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
<% header('Package ordered') %>
<SCRIPT TYPE="text/javascript">
// XXX fancy ajax rebuild table at some point, but a page reload will do for now
// XXX chop off trailing #target and replace... ?
- window.top.location = '<% popurl(3). "view/cust_main.cgi?keywords=$custnum;fragment=$frag#$frag" %>';
+ window.top.location = '<% popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" %>';
</SCRIPT>
@@ -16,8 +19,10 @@
%}
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Order customer package');
+ unless $curuser->access_right('Order customer package');
#untaint custnum (probably not necessary, searching for it is escape enough)
$cgi->param('custnum') =~ /^(\d+)$/
@@ -44,6 +49,10 @@ my $locationnum = $1;
my $cust_pkg = new FS::cust_pkg {
'custnum' => $custnum,
'pkgpart' => $pkgpart,
+ 'start_date' => ( scalar($cgi->param('start_date'))
+ ? str2time($cgi->param('start_date'))
+ : ''
+ ),
'refnum' => $refnum,
'locationnum' => $locationnum,
};
diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi
index 9993a87..59b5180 100755
--- a/httemplate/edit/process/svc_domain.cgi
+++ b/httemplate/edit/process/svc_domain.cgi
@@ -18,8 +18,8 @@ my $svcnum = $1;
my $new = new FS::svc_domain ( {
map {
$_, scalar($cgi->param($_));
- #} qw(svcnum pkgnum svcpart domain action purpose)
- } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) )
+ #} qw(svcnum pkgnum svcpart domain action)
+ } ( fields('svc_domain'), qw( pkgnum svcpart action ) )
} );
my $error = '';
diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html
index c18b2bc..c96fa6c 100644
--- a/httemplate/edit/quick-charge.html
+++ b/httemplate/edit/quick-charge.html
@@ -3,6 +3,11 @@
)
%>
+<LINK REL="stylesheet" TYPE="text/css" HREF="<%$fsurl%>elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/calendar-setup.js"></SCRIPT>
+
<% include('/elements/error.html') %>
<SCRIPT TYPE="text/javascript">
@@ -20,7 +25,7 @@ function validate_quick_charge () {
var pkg = document.QuickChargeForm.pkg.value;
var pkg_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ;
var amount = document.QuickChargeForm.amount.value;
- var amount_regex = /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ;
+ var amount_regex = /^\s*\$?\s*(\d*(\.?\d{1,2}))\s*$/ ;
var rval = true;
if ( ! amount_regex.test(amount) ) {
@@ -53,6 +58,23 @@ function validate_quick_charge () {
return false;
}
+function bill_now_changed (what) {
+ var form = what.form;
+ if ( what.checked ) {
+ form.start_date_text.disabled = true;
+ form.start_date.style.backgroundColor = '#dddddd';
+ form.start_date_button.style.display = 'none';
+ form.start_date_button_disabled.style.display = '';
+ form.invoice_terms.disabled = false;
+ } else {
+ form.start_date_text.disabled = false;
+ form.start_date.style.backgroundColor = '#ffffff';
+ form.start_date_button.style.display = '';
+ form.start_date_button_disabled.style.display = 'none';
+ form.invoice_terms.disabled = true;
+ }
+}
+
</SCRIPT>
<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" ID="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();">
@@ -79,6 +101,58 @@ function validate_quick_charge () {
<% include('/elements/tr-select-pkg_class.html', 'curr_value' => $cgi->param('classnum') ) %>
+<TR>
+ <TD ALIGN="right">Invoice now</TD>
+ <TD>
+ <INPUT TYPE = "checkbox"
+ NAME = "bill_now"
+ VALUE = "1"
+ <% $cgi->param('bill_now') ? 'CHECKED' : '' %>
+ onChange = "bill_now_changed(this);"
+ >
+ with terms
+ <% include('/elements/select-terms.html',
+ 'curr_value' => scalar($cgi->param('invoice_terms')),
+ 'empty_value' => $default_terms,
+ 'disabled' => ( $cgi->param('bill_now') ? 0 : 1 ),
+ )
+ %>
+ </TD>
+</TR>
+
+%# false laziness w/misc/order_pkg.html
+<TR>
+ <TD ALIGN="right">Charge date </TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "start_date"
+ SIZE = 32
+ ID = "start_date_text"
+ VALUE = "<% $start_date %>"
+ <% $cgi->param('bill_now') ? 'STYLE = "background-color:#dddddd" DISABLED' : '' %>
+ >
+ <IMG SRC = "<%$fsurl%>images/calendar.png"
+ ID = "start_date_button"
+ TITLE = "Select date"
+ STYLE = "cursor:pointer<% $cgi->param('bill_now') ? ';display:none' : '' %>"
+ >
+ <IMG SRC = "<%$fsurl%>images/calendar-disabled.png"
+ ID = "start_date_button_disabled"
+ <% $cgi->param('bill_now') ? '' : 'STYLE="display:none"' %>
+ >
+ <FONT SIZE=-1>(leave blank to charge immediately)</FONT>
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "start_date_text",
+ ifFormat: "%m/%d/%Y",
+ button: "start_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
<TR>
<TD ALIGN="right">Tax exempt </TD>
@@ -179,6 +253,11 @@ my $conf = new FS::Conf;
$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
my $custnum = $1;
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); #XXX agent-virt
+
+my $format = "%m/%d/%Y %T %z (%Z)"; #false laziness w/REAL_cust_pkg.cgi?
+my $start_date = $cust_main->next_bill_date;
+$start_date = $start_date ? time2str($format, $start_date) : '';
my $amount = '';
if ( $cgi->param('amount') =~ /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ) {
@@ -194,4 +273,14 @@ $cgi->param('pkg') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
or die 'illegal description';
my $pkg = $1;
+my $default_terms;
+if ( $cust_main->invoice_terms ) {
+ $default_terms = 'Customer default ('. $cust_main->invoice_terms. ')';
+} else {
+ $default_terms =
+ 'Default ('.
+ ($conf->config('invoice_default_terms') || 'Payable upon receipt').
+ ')';
+}
+
</%init>
diff --git a/httemplate/edit/reg_code.cgi b/httemplate/edit/reg_code.cgi
index e57ac09..76790ab 100644
--- a/httemplate/edit/reg_code.cgi
+++ b/httemplate/edit/reg_code.cgi
@@ -18,7 +18,7 @@ registration codes for <B><% $agent->agent %></B> allowing the following package
% my $pkgpart = $part_pkg->pkgpart;
<INPUT TYPE="checkbox" NAME="pkgpart<% $pkgpart %>" <% $cgi->param("pkgpart$pkgpart") ? 'CHECKED' : '' %>>
- <% $part_pkg->pkg %> - <% $part_pkg->comment %>
+ <% $part_pkg->pkg_comment %>
<BR>
% }
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
index 19e63b3..70eaa45 100755
--- a/httemplate/edit/router.cgi
+++ b/httemplate/edit/router.cgi
@@ -10,10 +10,12 @@
'fields' => [
{ 'field'=>'routername', 'type'=>'text', 'size'=>32 },
{ 'field'=>'agentnum', 'type'=>'select-agent' },
+ { 'field'=>'svcnum', 'type'=>'hidden' },
],
'error_callback' => $callback,
'edit_callback' => $callback,
'new_callback' => $callback,
+ 'html_table_bottom' => $html_table_bottom,
)
%>
<%init>
@@ -41,4 +43,12 @@ my $callback = sub {
}
};
+my $html_table_bottom = sub {
+ my $router = shift;
+ my $html = '';
+ foreach my $field ($router->virtual_fields) {
+ $html .= $router->pvf($field)->widget('HTML', 'edit', $router->get($field));
+ }
+ $html;
+};
</%init>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index 58283ef..b9a587d 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -168,7 +168,7 @@ Service # <% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
<TR>
- <TD ALIGN="right">GECOS</TD>
+ <TD ALIGN="right">Real Name</TD>
<TD>
<INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>">
</TD>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
index e60c76c..8a108f8 100644
--- a/httemplate/edit/svc_broadband.cgi
+++ b/httemplate/edit/svc_broadband.cgi
@@ -2,7 +2,7 @@
'post_url' => popurl(1). 'process/svc_broadband.cgi',
'name' => 'broadband service',
'table' => 'svc_broadband',
- 'labels' => { 'svcnum' => 'Service #',
+ 'labels' => { 'svcnum' => 'Service',
'description' => 'Description',
'ip_addr' => 'IP address',
'speed_down' => 'Download speed',
@@ -14,6 +14,7 @@
'longitude' => 'Longitude',
'altitude' => 'Altitude',
'vlan_profile' => 'VLAN profile',
+ 'performance_profile' => 'Performance profile',
'authkey' => 'Authentication key',
},
'fields' => \@fields,
@@ -34,7 +35,7 @@ my $conf = new FS::Conf;
my @fields = (
qw( description ip_addr speed_down speed_up blocknum ),
{ field=>'block_label', type=>'fixed' },
- qw( mac_addr latitude longitude altitude vlan_profile authkey )
+ qw( mac_addr latitude longitude altitude vlan_profile performance_profile authkey )
);
my $fixedblock = '';
diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
index 56ba604..10079ce 100755
--- a/httemplate/edit/svc_domain.cgi
+++ b/httemplate/edit/svc_domain.cgi
@@ -7,17 +7,31 @@
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
-<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>New
+<% ntable("#cccccc",2) %>
+<TR>
+<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63>
<BR>
+% if ($export) {
+Available top-level domains: <% $export->option('tlds') %>
+</TR>
-<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer
+<TR>
+<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>Register at <% $registrar->{'name'} %>
+<BR>
-<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63>
+<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer to <% $registrar->{'name'} %>
+<BR>
-<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="<% $purpose %>" SIZE=64>
+<INPUT TYPE="radio" NAME="action" VALUE="I"<% $kludge_action eq 'I' ? ' CHECKED' : '' %>>Registered elsewhere
-<P><INPUT TYPE="submit" VALUE="Submit">
+</TR>
+
+% }
+<TR>
+<P><INPUT TYPE="submit" VALUE="Submit">
+</TR>
+</TABLE>
</FORM>
<% include('/elements/footer.html') %>
@@ -27,7 +41,7 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
-my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc,
+my($svcnum, $pkgnum, $svcpart, $kludge_action, $part_svc,
$svc_domain);
if ( $cgi->param('error') ) {
@@ -38,7 +52,6 @@ if ( $cgi->param('error') ) {
$pkgnum = $cgi->param('pkgnum');
$svcpart = $cgi->param('svcpart');
$kludge_action = $cgi->param('action');
- $purpose = $cgi->param('purpose');
$part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
die "No part_svc entry!" unless $part_svc;
@@ -61,7 +74,6 @@ if ( $cgi->param('error') ) {
} else { #editing
$kludge_action = '';
- $purpose = '';
my($query) = $cgi->keywords;
$query =~ /^(\d+)$/ or die "unparsable svcnum";
$svcnum=$1;
@@ -82,6 +94,20 @@ my $action = $svcnum ? 'Edit' : 'Add';
my $svc = $part_svc->getfield('svc');
+my @exports = $part_svc->part_export();
+
+my $registrar;
+my $export;
+
+# Find the first export that does domain registration
+foreach (@exports) {
+ $export = $_ if $_->can('registrar');
+}
+# If we have a domain registration export, get the registrar object
+if ($export) {
+ $registrar = $export->registrar;
+}
+
my $otaker = getotaker;
my $domain = $svc_domain->domain;
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
index eeb6f67..cd4db75 100644
--- a/httemplate/edit/svc_www.cgi
+++ b/httemplate/edit/svc_www.cgi
@@ -38,7 +38,7 @@ Service #<B><% $svcnum ? $svcnum : "(NEW)" %></B>
% foreach $_ (keys %svc_acct) {
<OPTION<% ($_ eq $usersvc) ? " SELECTED" : "" %> VALUE="<%$_%>"><% $svc_acct{$_} %>
% }
- <SELECT>
+ </SELECT>
</TD>
</TR>
% }
diff --git a/httemplate/elements/about_freeside.html b/httemplate/elements/about_freeside.html
new file mode 100644
index 0000000..8084583
--- /dev/null
+++ b/httemplate/elements/about_freeside.html
@@ -0,0 +1,19 @@
+<FONT SIZE="-2" STYLE="color:#999999">
+ <% include('/elements/popup_link.html',
+ 'action' => $fsurl.'docs/about.html',
+ 'label' => 'Freeside',
+ 'style' => 'color:#999999',
+ 'actionlabel' => 'About',
+ 'width' => 300,
+ 'height' => 360,
+ 'color' => '#7e0079',
+ 'scrolling' => 'no',
+ ) |n
+ %>&nbsp;v<% $FS::VERSION %><BR>
+ <A HREF="<% $conf->config('support-key') ? "http://www.freeside.biz/mediawiki/index.php/Supported:Documentation" : "http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation" %>" TARGET="_blank" STYLE="color:#999999">Documentation</A>
+</FONT>
+<%init>
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/about_rt.html b/httemplate/elements/about_rt.html
new file mode 100644
index 0000000..e3ee140
--- /dev/null
+++ b/httemplate/elements/about_rt.html
@@ -0,0 +1,13 @@
+% if ( $conf->config('ticket_system') eq 'RT_Internal' ) {
+% eval "use RT;";
+
+<FONT SIZE="-2" STYLE="color:#999999">
+ <A HREF="http://www.bestpractical.com/rt" TARGET="_blank" STYLE="color:#999999">RT<A>&nbsp;v<% $RT::VERSION %><BR>
+ <A HREF="http://wiki.bestpractical.com/" TARGET="_blank" STYLE="color:#999999">Documentation</A>
+</FONT>
+% }
+<%init>
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/checkbox.html b/httemplate/elements/checkbox.html
new file mode 100644
index 0000000..5176070
--- /dev/null
+++ b/httemplate/elements/checkbox.html
@@ -0,0 +1,19 @@
+<% $opt{'prefix'} %><INPUT TYPE = "checkbox"
+ NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ VALUE = "<% $opt{value} %>"
+ <% $opt{curr_value} eq $opt{value}
+ ? ' CHECKED'
+ : ''
+ %>
+ <% $onchange %>
+ ><% $opt{'postfix'} %>
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+</%init>
diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html
index 1262245..b120ada 100644
--- a/httemplate/elements/checkboxes.html
+++ b/httemplate/elements/checkboxes.html
@@ -10,8 +10,9 @@ Example:
'names_list' => [ 'value',
'other value',
- [ 'complex value' => { 'desc' => "Add'l description",
- 'note' => '&nbsp;*',
+ [ 'complex value' => { 'label' => 'Display value',
+ 'desc' => "Add'l description",
+ 'note' => '&nbsp;*',
}
],
],
@@ -40,7 +41,10 @@ Example:
% foreach my $item ( @{ $opt{'names_list'} } ) {
%
% my $name = ref($item) ? $item->[0] : $item;
-% ( my $display = $name ) =~ s/ /&nbsp;/g;
+% my $display = ( ref($item) && $item->[1]{label} )
+% ? $item->[1]{label}
+% : $name;
+% $display =~ s/ /&nbsp;/g;
% $display .= $item->[1]{note} if ref($item) && $item->[1]{note};
% my $desc = ref($item) && $item->[1]{desc} ? $item->[1]{desc} : '';
%
diff --git a/httemplate/elements/file-upload.html b/httemplate/elements/file-upload.html
index c8b026d..7e2eeef 100644
--- a/httemplate/elements/file-upload.html
+++ b/httemplate/elements/file-upload.html
@@ -55,7 +55,7 @@
% foreach (@field) {
<TR>
- <TH ALIGN="right"><% shift @label %></TH>
+ <TH ALIGN="<% $param{'label_align'} || 'right' %>"><% shift @label %></TH>
<TD><INPUT TYPE="file" NAME="<% $_ %>" /></TD>
</TR>
% }
diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html
index 68be108..d74581b 100644
--- a/httemplate/elements/header-popup.html
+++ b/httemplate/elements/header-popup.html
@@ -1,10 +1,3 @@
-%
-% my($title, $menubar) = ( shift, shift ); #$menubar is unused here though
-% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
-% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
-% my $conf = new FS::Conf;
-%
-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
@@ -21,4 +14,30 @@
<FONT SIZE=6>
<CENTER><% $title %></CENTER>
</FONT>
+
+% unless ( $nobr ) {
<BR><!--<BR>-->
+% }
+
+<%init>
+
+my( $title, $menubar, $etc, $head ) = ( '', '', '', '' );
+#my( $nobr, $nocss ) = ( 0, 0 );
+my $nobr = 0;
+if ( ref($_[0]) ) {
+ my $opt = shift;
+ $title = $opt->{title};
+ $menubar = $opt->{menubar};
+ $etc = $opt->{etc};
+ $head = $opt->{head};
+ $nobr = $opt->{nobr};
+# $nocss = $opt->{nocss};
+} else {
+ ($title, $menubar) = ( shift, shift );
+ $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+ $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+}
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
index 8e902f0..b9ddc73 100644
--- a/httemplate/elements/header.html
+++ b/httemplate/elements/header.html
@@ -1,4 +1,26 @@
+<%doc>
+
+Example:
+
+ include( '/elements/header.html',
+ {
+ 'title' => 'Title',
+ 'menubar' => \@menubar,
+ 'etc' => '', #included in <BODY> tag, for things like onLoad=
+ 'head' => '', #included before closing </HEAD> tag
+ 'nobr' => 0, #1 for no <BR><BR> after the title
+ }
+ );
+
+ #old-style
+ include( '/elements/header.html', 'Title', $menubar, $etc, $head);
+
+
+</%doc>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+%# above is what RT declares, should we switch now? hopefully no glitches result
+%# or just fuck it, XHTML died anyway, HTML 5 or bust?
<HTML>
<HEAD>
<TITLE>
@@ -10,6 +32,7 @@
<% include('menu.html', 'freeside_baseurl' => $fsurl,
'position' => $menu_position,
+ 'nocss' => $nocss,
) |n
%>
@@ -17,7 +40,7 @@
<SCRIPT TYPE="text/javascript">
function clearhint_search_cust (what) {
- if ( what.value == '(cust #, name, company or phone)' )
+ if ( what.value == '(cust #, name, company or contact phone)' )
what.value = '';
}
@@ -32,7 +55,7 @@
}
function clearhint_search_svc (what) {
- if ( what.value == '(user, email, ip, mac, or domain)' )
+ if ( what.value == '(user, email, ip, mac, domain or service phone)' )
what.value = '';
}
@@ -48,49 +71,16 @@
<BODY <% $menu_position eq 'left' ? qq( BACKGROUND="${fsurl}images/background-cheat.png" ) : ' BGCOLOR="#e8e8e8" ' %> <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0">
<table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0; padding-right:4">
<tr>
- <td rowspan=2 BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" SRC="<%$fsurl%>view/REAL_logo.cgi"></td>
- <td align=left rowspan=2 BGCOLOR="#ffffff"> <!-- valign="top" -->
+ <td BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"></td>
+ <td align=left BGCOLOR="#ffffff"> <!-- valign="top" -->
<font size=6><% $company_name || 'ExampleCo' %></font>
</td>
- <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html">Preferences</a>&nbsp;<BR></FONT>
- </td>
- </tr>
- <tr>
- <td align=right valign=bottom BGCOLOR="#ffffff">
-
- <table>
- <tr>
- <td align=right BGCOLOR="#ffffff">
- <FONT SIZE="-2">
- <% include('/elements/popup_link.html',
- 'action' => $fsurl.'docs/about.html',
- 'label' => 'Freeside',
- 'actionlabel' => 'About',
- 'width' => 300,
- 'height' => 360,
- 'color' => '#7e0079',
- 'scrolling' => 'no',
- ) |n
- %>&nbsp;v<% $FS::VERSION %><BR>
- <A HREF="<% $conf->config('support-key') ? "http://www.freeside.biz/mediawiki/index.php/Supported:Documentation" : "http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation" %>" TARGET="_blank">Documentation</A><BR>
- </FONT>
- </td>
-% if ( $conf->config('ticket_system') eq 'RT_Internal' ) {
-% eval "use RT;";
-
- <td bgcolor=#000000></td>
- <td align=left>
- <FONT SIZE="-2">
- <A HREF="http://www.bestpractical.com/rt" TARGET="_blank">RT<A>&nbsp;v<% $RT::VERSION %><BR>
- <A HREF="http://wiki.bestpractical.com/" TARGET="_blank">Documentation</A><BR>
- </FONT>
- </td>
-% }
-
-
- </tr>
- </table>
-
+ <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a>
+% if ( $conf->config("ticket_system")
+% && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) {
+ | <a href="<%$fsurl%>rt/User/Prefs.html" STYLE="color: #000000">Ticketing preferences</a>
+% }
+ <BR></FONT>
</td>
</tr>
</table>
@@ -104,9 +94,16 @@ input.fsblackbutton {
border-left-color:#cccccc;
border-right-color:#aaaaaa;
border-bottom-color:#aaaaaa;
+ font-family: Arial, Verdana, Helvetica, sans-serif;
font-weight:bold;
padding-left:12px;
padding-right:12px;
+ padding-top:0px;
+ padding-bottom:0px;
+ margin-left:0px;
+ margin-right:0px;
+ margin-top:2px;
+ margin-bottom:0px;
overflow:visible;
filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666')
}
@@ -119,12 +116,40 @@ input.fsblackbuttonselected {
border-left-color:#cccccc;
border-right-color:#aaaaaa;
border-bottom-color:#aaaaaa;
+ font-family: Arial, Verdana, Helvetica, sans-serif;
font-weight:bold;
padding-left:12px;
padding-right:12px;
+ padding-top:0px;
+ padding-bottom:0px;
+ margin-left:0px;
+ margin-right:0px;
+ margin-top:2px;
+ margin-bottom:0px;
overflow:visible;
filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079')
}
+
+input.fstext {
+ border: 2px inset #eee;
+ /*border-top-color:#aaaaaa;
+ border-left-color:#aaaaaa;
+ border-right-color:#cccccc;
+ border-bottom-color:#cccccc;
+ */
+ vertical-align:bottom;
+ text-align:right;
+ font-family: Arial,Verdana,Helvetica,sans-serif;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ margin-left:0px;
+ margin-right:0px;
+ margin-top:0px;
+ margin-bottom:1px;
+}
+
</style>
<TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0>
@@ -136,12 +161,22 @@ input.fsblackbuttonselected {
<TR>
- <TD COLSPAN="6" WIDTH="100%" STYLE="padding:0">
+ <TD COLSPAN="4" WIDTH="100%" STYLE="padding:0" BGCOLOR="#000000">
<SCRIPT TYPE="text/javascript">
document.write(myBar);
</SCRIPT>
</TD>
+
+ <TD COLSPAN="2" ALIGN="right" STYLE="padding:0px 8px 0px 0px" BGCOLOR="#000000">
+ <TABLE CELLSPACING=0 CELLPADDING=0 BGCOLOR="#000000" BORDER=0>
+ <TR>
+ <TD ALIGN="right" STYLE="padding-right:3px;padding-bottom:1px;border-right:1px solid #999999"><% include('/elements/about_freeside.html') |n %></TD>
+ <TD ALIGN="left" STYLE="padding-left:3px;padding-bottom:1px"><% include('/elements/about_rt.html') |n %></TD>
+ </TR>
+ </TABLE>
+ </TD>
+
</TR>
<TR>
@@ -159,17 +194,11 @@ input.fsblackbuttonselected {
<TR>
<TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
- <FORM ACTION="<%$fsurl%>edit/cust_main.cgi" METHOD="GET" STYLE="margin:0">
- <INPUT TYPE="submit" VALUE="New customer" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="vertical-align:bottom; font-size:100%">
- </FORM>
- </TD>
-
- <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
% if ( $curuser->access_right('List customers') ) {
<FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0">
- <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
- <A HREF="<%$fsurl%>search/report_cust_main.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A>
- <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+ <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or contact phone)" SIZE="37" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" CLASS="fstext"><BR>
+ <A HREF="<%$fsurl%>search/report_cust_main.html" STYLE="color: #ffffff; font-size: 11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
% }
</TD>
@@ -178,9 +207,9 @@ input.fsblackbuttonselected {
% if ( $conf->exists('address2-search') ) {
<FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0;display:inline">
<INPUT TYPE="hidden" NAME="address2_on" VALUE="1">
- <INPUT NAME="address2_text" TYPE="text" VALUE="(Unit #)" SIZE="4" onFocus="clearhint_search_address2(this);" onClick="clearhint_search_address2(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px">
+ <INPUT NAME="address2_text" TYPE="text" VALUE="(Unit #)" SIZE="4" onFocus="clearhint_search_address2(this);" onClick="clearhint_search_address2(this);" CLASS="fstext">
<BR>
- <INPUT TYPE="submit" VALUE="Search units" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px">
+ <INPUT TYPE="submit" VALUE="Search units" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:2px;padding-right:2px">
</FORM>
% }
</TD>
@@ -189,32 +218,32 @@ input.fsblackbuttonselected {
% if ( $curuser->access_right('View invoices') ) {
<FORM ACTION="<%$fsurl%>search/cust_bill.html" METHOD="GET" STYLE="margin:0;display:inline">
- <INPUT NAME="invnum" TYPE="text" VALUE="(inv #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px">
+ <INPUT NAME="invnum" TYPE="text" VALUE="(inv #)" SIZE="5" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" CLASS="fstext">
% if ( $curuser->access_right('List invoices') ) {
-
- <A HREF="<%$fsurl%>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A>
+ <A HREF="<%$fsurl%>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 11px">Adv</A>
% }
-
<BR>
- <INPUT TYPE="submit" VALUE="Search invoices" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+ <INPUT TYPE="submit" VALUE="Search invoices" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px;padding-left:1px;padding-right:1px">
</FORM>
% }
</TD>
<TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
+% if ( $curuser->access_right('View customer services') ) {
<FORM ACTION="<%$fsurl%>search/cust_svc.html" METHOD="GET" STYLE="margin:0">
- <INPUT NAME="search_svc" TYPE="text" VALUE="(user, email, ip, mac, or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
- <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">Advanced</A>
- <INPUT TYPE="submit" VALUE="Search services" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+ <INPUT NAME="search_svc" TYPE="text" VALUE="(user, email, ip, mac, domain or service phone)" SIZE="41" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" CLASS="fstext"><BR>
+ <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size:11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search services" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
+% }
</TD>
- <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right" STYLE="padding-right:4px">
+ <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right" STYLE="padding-left:4px;padding-right:4px">
% if ( $conf->config("ticket_system") ) {
<FORM ACTION="<% FS::TicketSystem->baseurl %>index.html" METHOD="GET" STYLE="margin:0">
- <INPUT NAME="q" TYPE="text" VALUE="(ticket #, subject, email or fulltext:text)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
- <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A>
- <INPUT TYPE="submit" VALUE="Search tickets" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px">
+ <INPUT NAME="q" TYPE="text" VALUE="(ticket #, subject, email or fulltext:text)" SIZE=33 onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" CLASS="fstext"><BR>
+ <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" STYLE="color: #ffffff; font-size:11px">Advanced</A>
+ <INPUT TYPE="submit" VALUE="Search tickets" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
% }
</TD>
@@ -248,6 +277,15 @@ input.fsblackbuttonselected {
<BR>
<IMG SRC="<%$fsurl%>images/32clear.gif" HEIGHT="1" WIDTH="154">
+ <TABLE CELLSPACING=0 CELLPADDING=0 BGCOLOR="#000000" WIDTH="100%">
+ <TR>
+ <TD ALIGN="left" STYLE="padding-bottom:1px;border-bottom:1px solid #999999"><% include('/elements/about_freeside.html') |n %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right" STYLE="padding-bottom:1px"><% include('/elements/about_rt.html') |n %></TD>
+ </TR>
+ </TABLE>
+
</TD>
<TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-side.png"></TD>
@@ -267,7 +305,7 @@ input.fsblackbuttonselected {
<%init>
my( $title, $menubar, $etc, $head ) = ( '', '', '', '' );
-my( $nobr ) = ( 0 );
+my( $nobr, $nocss ) = ( 0, 0 );
if ( ref($_[0]) ) {
my $opt = shift;
$title = $opt->{title};
@@ -275,6 +313,7 @@ if ( ref($_[0]) ) {
$etc = $opt->{etc};
$head = $opt->{head};
$nobr = $opt->{nobr};
+ $nocss = $opt->{nocss};
} else {
($title, $menubar) = ( shift, shift );
$etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index d7b73a2..07aaa69 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -3,26 +3,28 @@
Example:
include( '/elements/location.html',
- 'object' => $cust_main, # or $cust_location
- 'prefix' => $pre, #only for cust_main objects
- 'onchange' => $javascript,
- 'disabled' => $disabled,
- 'same_checked' => $same_checked,
- 'geocode' => $geocode, #passed through
- 'no_asterisks' => 0, #set true to disable the red asterisks next
- #to required fields
+ 'object' => $cust_main, # or $cust_location
+ 'prefix' => $pre, #only for cust_main objects
+ 'onchange' => $javascript,
+ 'disabled' => $disabled,
+ 'same_checked' => $same_checked,
+ 'geocode' => $geocode, #passed through
+ 'censustract' => $censustract, #passed through
+ 'no_asterisks' => 0, #set true to disable the red asterisks next
+ #to required fields
+ 'address1_label' => 'Address', #label for address
)
</%doc>
<TR>
- <TH ALIGN="right"><%$r%>Address</TH>
+ <TH ALIGN="right"><%$r%><% $opt{'address1_label'} || 'Address' %></TH>
<TD COLSPAN=7>
<INPUT TYPE = "text"
NAME = "<%$pre%>address1"
ID = "<%$pre%>address1"
VALUE = "<% $object->get($pre.'address1') |h %>"
- SIZE = 58
+ SIZE = 54
onChange = "<% $onchange %>"
<% $disabled %>
<% $style %>
@@ -37,7 +39,7 @@ Example:
NAME = "<%$pre%>address2"
ID = "<%$pre%>address2"
VALUE = "<% $object->get($pre.'address2') |h %>"
- SIZE = 58
+ SIZE = 54
onChange = "<% $onchange %>"
<% $disabled %>
<% $style %>
@@ -47,7 +49,7 @@ Example:
<TR>
<TH ALIGN="right"><%$r%>City</TH>
- <TD>
+ <TD WIDTH="1">
<INPUT TYPE = "text"
NAME = "<%$pre%>city"
ID = "<%$pre%>city"
@@ -56,13 +58,11 @@ Example:
<% $disabled %>
<% $style %>
>
- </TD>
- <TH ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</TH>
- <TD>
- <% include('/elements/select-county.html', %select_hash ) %>
</TD>
- <TH ALIGN="right"><%$r%>State</TH>
- <TD>
+ <TH ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</TH>
+ <TD><% include('/elements/select-county.html', %select_hash ) %></TD>
+ <TH ALIGN="right" WIDTH="1"><%$r%>State</TH>
+ <TD WIDTH="1">
<% include('/elements/select-state.html', %select_hash ) %>
</TD>
<TH><%$r%>Zip</TH>
@@ -81,11 +81,21 @@ Example:
<TR>
<TH ALIGN="right"><%$r%>Country</TH>
- <TD COLSPAN=5><% include('/elements/select-country.html', %select_hash ) %></TD>
+ <TD COLSPAN=6><% include('/elements/select-country.html', %select_hash ) %></TD>
</TR>
% if ( !$pre ) {
<INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>">
+% } else {
+% if ( $pre eq 'ship_' && $conf->exists('cust_main-require_censustract') ) {
+ <TR><TH ALIGN="right">Census tract<BR>(automatic)</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="censustract" VALUE="<% $opt{censustract} %>">
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>">
+% }
% }
<%init>
@@ -125,7 +135,7 @@ my @counties = counties( $object->get($pre.'state'),
$object->get($pre.'country'),
);
my @county_style = ();
-push @county_style, 'visibility:hidden'
+push @county_style, 'display:none' # 'visibility:hidden'
unless scalar(@counties) > 1;
my $style =
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 627f9c8..c54ed07 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -12,7 +12,9 @@
% }
-<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+% unless ( $opt{'nocss'} ) {
+ <link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+% }
<SCRIPT TYPE="text/javascript">
@@ -33,8 +35,8 @@
%
% my( $subhtml, $submenuname ) = submenu($url_or_submenu, $item);
- <% $subhtml %>
- myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname %> ));
+ <% $subhtml |n %>
+ myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname |n %> ));
% } else {
@@ -56,7 +58,7 @@ my $fsurl = $opt{'freeside_baseurl'};
my $curuser = $FS::CurrentUser::CurrentUser;
-#Active tickets not assigned to a customer
+#XXX Active tickets not assigned to a customer
tie my %report_customers_lists, 'Tie::IxHash',
'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ],
@@ -133,10 +135,19 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
}
if ( $svcdb eq 'svc_acct' ) {
+
$report_svc{"All $lcname never logged in"} =
[ svc_url( %svc_url, 'query' => "magic=nologin;sortby=svcnum" ),
'',
];
+
+ } elsif ( $svcdb eq 'svc_phone' ) {
+
+ $report_svc{"${name}' total usage by time period"} =
+ [ $fsurl. 'search/report_svc_phone.html',
+ 'Total usage (minutes, and amount billed) for the specified time period, per phone number.',
+ ];
+
}
if ( $curuser->access_right('View/link unlinked services') ) {
@@ -170,6 +181,8 @@ if ( $curuser->access_right('Financial reports') ) {
$report_packages{'All customer packages'} = [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ];
$report_packages{'Suspended customer packages'} = [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ];
$report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ];
+$report_packages{'FCC Form 477 packages'} = [ $fsurl.'search/report_477.html', 'Summarize packages by census tract for particular types' ]
+ if $conf->exists('cust_main-require_censustract');
$report_packages{'Advanced package reports'} = [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ];
tie my %report_rating, 'Tie::IxHash',
@@ -178,57 +191,106 @@ tie my %report_rating, 'Tie::IxHash',
'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ],
;
+tie my %report_ticketing_statistics, 'Tie::IxHash',
+ 'Tickets per day per Queue' => [ $fsurl.'rt/RTx/Statistics/CallsQueueDay', 'View the number of tickets created, resolved or deleted in a specific Queue, over the requested period of days' ],
+ 'Ticket status by Queue' => [ $fsurl.'rt/RTx/Statistics/OpenStalled', 'View numbers of new, open and stalled tickets in a selected Queue' ],
+ 'Tickets per day (multiple Queues)' => [ $fsurl.'rt/RTx/Statistics/CallsMultiQueue', 'View tickets created, resolved or deleted on in one or more Queues over a specified time period' ],
+ 'Tickets per Day of Week' => [ $fsurl.'rt/RTx/Statistics/DayOfWeek', 'View trends showing when tickets are created, resolved or deleted' ],
+ 'Time to resolve' => [ $fsurl.'rt/RTx/Statistics/Resolution', 'View how long tickets take to be resolved by Queue' ],
+ 'Time to resolve (scatter graph)' => [ $fsurl.'rt/RTx/Statistics/TimeToResolve', 'View a detailed scatter graph of time to resolve tickets by Queue' ],
+;
+
+tie my %report_ticketing, 'Tie::IxHash',
+ 'Resolved by owner' => [ $fsurl.'rt/Tools/Reports/ResolvedByOwner.html', '' ],
+ 'Resolved in date range' => [ $fsurl.'rt/Tools/Reports/ResolvedByDates.html', '' ],
+ 'Created in date range' => [ $fsurl.'rt/Tools/Reports/CreatedByDates.html', '' ],
+ 'separator' => '',
+ 'Statistics' => [ \%report_ticketing_statistics, '' ],
+ 'separator2' => '',
+ 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html', 'List tickets by any criteria' ],
+;
+
tie my %report_bill_event, 'Tie::IxHash',
'All billing events' => [ $fsurl.'search/report_cust_event.html', 'All billing events for a date range' ],
'Billing event errors' => [ $fsurl.'search/report_cust_event.html?failed=1', 'Failed credit cards, processor or printer problems, etc.' ],
- 'All invoice events' => [ $fsurl.'search/cust_bill_event.html', 'Reports on deprecated, old-style invoice events for a date range' ],
- 'Invoice event errors' => [ $fsurl.'search/cust_bill_event.html?failed=1', 'Reports on deprecated, old-style events for failed credit cards, processor or printer problems, etc.' ],
+# 'All invoice events' => [ $fsurl.'search/cust_bill_event.html', 'Reports on deprecated, old-style invoice events for a date range' ],
+# 'Invoice event errors' => [ $fsurl.'search/cust_bill_event.html?failed=1', 'Reports on deprecated, old-style events for failed credit cards, processor or printer problems, etc.' ],
;
-tie my %report_financial, 'Tie::IxHash',
- 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ],
- 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
- 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
- 'Payment Report' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
+tie my %report_payments, 'Tie::IxHash',
+ 'Payments' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
;
-$report_financial{'Pending Payment Report'} = [ $fsurl.'search/cust_pay_pending.html?magic=_date;statusNOT=done', 'Pending real-time payments' ]
+$report_payments{'Pending Payments'} = [ $fsurl.'search/cust_pay_pending.html?magic=_date;statusNOT=done', 'Pending real-time payments' ]
if $curuser->access_right('View customer pending payments');
-$report_financial{'Payment Batch Report'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ]
+$report_payments{'Voided Payments'} = [ $fsurl.'search/report_cust_pay.html?void=1', 'Voided payment report (by type and/or date range)' ]
+ if $curuser->access_right('View customer pending payments');
+$report_payments{'Payment Batches'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ]
if $conf->exists('batch-enable') || $conf->config('batch-enable_payby');
-$report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
-$report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ];
-$report_financial{'Sales Tax Liability'} = [ $fsurl.'search/report_tax.html', 'Sales tax liability report (old taxclass system)' ];
-$report_financial{'Tax Liability'} = [ $fsurl.'search/report_newtax.html', 'Tax liability report (new tax products system)' ]
- if $conf->exists('enable_taxproducts');
-;
+$report_payments{'Unapplied Payment Aging'} = [ $fsurl.'search/report_unapplied_cust_pay.html', 'Unapplied payment aging report' ];
+
+tie my %report_financial, 'Tie::IxHash';
+if($curuser->access_right('Financial reports')) {
+
+ %report_financial = (
+ 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts 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)' ],
+ 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
+ );
+ $report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
+ $report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ];
+ $report_financial{'Sales Tax Liability'} = [ $fsurl.'search/report_tax.html', 'Sales tax liability report (internal taxclass system)' ];
+ $report_financial{'Tax Liability'} = [ $fsurl.'search/report_newtax.html', 'Tax liability report (vendor data tax products system)' ]
+ if $conf->exists('enable_taxproducts');
+
+} elsif($curuser->access_right('Receivables report')) {
+
+ $report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ];
+
+} # else $report_financial contains nothing.
tie my %report_menu, 'Tie::IxHash';
$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ]
if $curuser->access_right('List customers');
$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ]
if $curuser->access_right('List invoices');
+$report_menu{'Payments'} = [ \%report_payments, 'Payment reports' ]
+ if $curuser->access_right('Financial reports');
$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ]
if $curuser->access_right('List packages');
$report_menu{'Services'} = [ \%report_services, 'Services reports' ]
if $curuser->access_right('List services');
$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ]
if $curuser->access_right('List rating data');
+$report_menu{'Tickets'} = [ \%report_ticketing, 'Ticket reports' ]
+ if $conf->config('ticket_system')
+ ;#&& FS::TicketSystem->access_right(\%session, 'Something');
$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ]
if $curuser->access_right('Billing event reports');
$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ]
- if $curuser->access_right('Financial reports');
+ if $curuser->access_right('Financial reports')
+ or $curuser->access_right('Receivables report');
$report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query' ]
if $curuser->access_right('Raw SQL');
tie my %tools_importing, 'Tie::IxHash',
- 'Import customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
- 'Import customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
- 'Import one-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
- 'Import payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
- 'Import phone numbers (DIDs)' => [ $fsurl.'misc/phone_avail-import.html', '' ],
- 'Import Call Detail Records (CDRs) from CSV file' => [ $fsurl.'misc/cdr-import.html', '' ],
- 'Import tax rates from CSV files' => [ $fsurl.'misc/tax-import.cgi', '' ],
+ 'Customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
+ 'Customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
+ 'One-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
+ 'Payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
+ 'Phone numbers (DIDs)' => [ $fsurl.'misc/phone_avail-import.html', '' ],
+ 'Call Detail Records (CDRs)' => [ $fsurl.'misc/cdr-import.html', '' ],
+# 'Import call rates and regions' => [ $fsurl.'misc/rate-import.html', '' ],
;
+if ( $conf->exists('enable_taxproducts') ) {
+ if ( $conf->exists('taxdatadirectdownload') ) {
+ $tools_importing{'Tax rates from vendor site'} =
+ [ $fsurl.'misc/tax-fetch_and_import.cgi', '' ];
+ } else {
+ $tools_importing{'Tax rates from CSV files'} =
+ [ $fsurl.'misc/tax-import.cgi', '' ];
+ }
+}
tie my %tools_exporting, 'Tie::IxHash',
'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ],
@@ -239,6 +301,14 @@ tie my %tools_exporting, 'Tie::IxHash',
# <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A>
# <BR> -->
+tie my %tools_ticketing, 'Tie::IxHash',
+ 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ],
+ 'My Day' => [ $fsurl.'rt/Tools/MyDay.html', '' ],
+ 'My Approvals' => [ $fsurl.'rt/Approvals/', '' ],
+;
+$tools_ticketing{'Cron Tool'} = [ $fsurl.'rt/Developer/CronTool/', '' ]
+ if $conf->exists('rt-crontool');
+
tie my %tools_menu, 'Tie::IxHash', ();
$tools_menu{'Quick payment entry'} = [ $fsurl.'misc/batch-cust_pay.html', 'Enter multiple payments in a batch' ]
if $curuser->access_right('Post payment batch');
@@ -247,6 +317,8 @@ $tools_menu{'Process payment batches'} = [ $fsurl.'search/pay_batch.cgi?magic=_d
&& $curuser->access_right('Process batches');
$tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queue' ]
if $curuser->access_right('Job queue');
+$tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ]
+ if $conf->config('ticket_system');
$tools_menu{'Time Queue'} = [ $fsurl.'search/timeworked.html', 'View pending support time' ]
if $curuser->access_right('Time queue');
$tools_menu{'Importing'} = [ \%tools_importing, 'Import tools' ]
@@ -255,72 +327,98 @@ $tools_menu{'Exporting'} = [ \%tools_exporting, 'Export tools' ]
if $curuser->access_right('Export');
tie my %config_employees, 'Tie::IxHash',
- 'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ],
- 'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ],
+ 'Employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ],
+ 'Employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ],
;
-tie my %config_export_svc_pkg, 'Tie::IxHash', ();
+tie my %config_export_svc, 'Tie::IxHash', ();
if ( $curuser->access_right('Configuration') ) {
- $config_export_svc_pkg{'View/Edit exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
- $config_export_svc_pkg{'View/Edit service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
+ $config_export_svc{'Exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
+ $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
}
-$config_export_svc_pkg{'View/Edit package definitions'} = [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ]
+
+tie my %config_pkg, 'Tie::IxHash', ();
+$config_pkg{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ]
if $curuser->access_right('Edit package definitions')
|| $curuser->access_right('Edit global package definitions');
if ( $curuser->access_right('Configuration') ) {
- $config_export_svc_pkg{'View/Edit package categories'} = [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes, for reporting and convenience purposes.' ];
- $config_export_svc_pkg{'View/Edit package classes'} = [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ];
- $config_export_svc_pkg{'View/Edit cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ];
- $config_export_svc_pkg{'View/Edit cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
- $config_export_svc_pkg{'View/Edit suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ];
- $config_export_svc_pkg{'View/Edit suspend reasons'} = [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ];
+ $config_pkg{'Package categories'} = [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes, for reporting and convenience purposes.' ];
+ $config_pkg{'Package tax classes'} = [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ];
+ $config_pkg{'Package report classes'} = [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting purposes.' ];
+ $config_pkg{'Cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ];
+ $config_pkg{'Cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
+ $config_pkg{'Suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ];
+ $config_pkg{'Suspend reasons'} = [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ];
}
tie my %config_agent, 'Tie::IxHash',
- 'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
- 'View/Edit agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ],
- 'View/Edit agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ];
+ 'Agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
+ 'Agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ],
+ 'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ];
;
tie my %config_billing_rates, 'Tie::IxHash',
- 'View/Edit rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
- 'View/Edit regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
- 'View/Edit usage classes' => [ $fsurl.'browse/usage_class.html', 'Usage classes define groups of usage for taxation purposes.' ],
+ 'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
+ 'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
+ 'Usage classes' => [ $fsurl.'browse/usage_class.html', 'Usage classes define groups of usage for taxation purposes.' ],
+ 'Edit rates with Excel' => [ $fsurl.'misc/rate_edit_excel.html', 'Download and edit rates with Excel, then upload changes.' ], #"Edit with Excel" ?
;
tie my %config_billing, 'Tie::IxHash';
-# 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ];
-$config_billing{'View/Edit billing events'} = [ $fsurl.'browse/part_event.html', 'Billing actions for customers, invoices and packages' ]
+# 'Payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ];
+$config_billing{'Billing events'} = [ $fsurl.'browse/part_event.html', 'Billing actions for customers, invoices and packages' ]
if $curuser->access_right('Edit billing events')
|| $curuser->access_right('Edit global billing events');
if ( $curuser->access_right('Configuration') ) {
- $config_billing{'View/Edit invoice events'} = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ];
- $config_billing{'View/Edit invoice templates'} = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ];
- $config_billing{'View/Edit prepaid cards'} = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ];
- $config_billing{'View/Edit call rates and regions'} = [ \%config_billing_rates, 'Manage rate plans, regions and prefixes for VoIP and call billing' ];
- $config_billing{'View/Edit locales and tax rates (old tax class system)'} = [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ];
- $config_billing{'View/Edit tax rates (new tax products system)'} = [ $fsurl.'browse/tax_rate.cgi', 'Edit tax rates for the new tax products system' ];
- $config_billing{'View/Edit credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons, for reporting and convenience purposes.' ];
- $config_billing{'View/Edit credit reasons'} = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
+ #$config_billing{'Invoice events'} = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ];
+ $config_billing{'Invoice templates'} = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ];
+ $config_billing{'Prepaid cards'} = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ];
+ $config_billing{'Call rates and regions'} = [ \%config_billing_rates, 'Manage rate plans, regions and prefixes for VoIP and call billing' ];
+
+ my $config_taxes_name = 'Locales and tax rates'.
+ ( $conf->exists('enable_taxproducts')
+ ? ' (internal tax class system)'
+ : ''
+ );
+ $config_billing{$config_taxes_name} = [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ];
+ $config_billing{'Tax rates (vendor data tax products system)'} = [ $fsurl.'browse/tax_rate.cgi', 'Edit tax rates for the vendor data tax products system' ]
+ if $conf->exists('enable_taxproducts');
+ $config_billing{'Tax classes'} = [ $fsurl. 'browse/part_pkg_taxclass.html', 'Tax classes' ];
+
+ $config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons, for reporting and convenience purposes.' ];
+ $config_billing{'Credit reasons'} = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
}
+tie my %config_ticketing, 'Tie::IxHash',
+ 'Ticketing Users' => [ $fsurl.'rt/Admin/Users', 'View/Edit ticketing users' ], #XXX to be unified
+ 'Ticketing Groups' => [ $fsurl.'rt/Admin/Groups', 'View/Edit ticketing groups and group membership' ], #XXX to be unified
+ 'Ticketing Queues' => [ $fsurl.'rt/Admin/Queues', 'View/Edit ticketing queues and queue-specific properties' ],
+ 'Ticket Custom Fields' => [ $fsurl.'rt/Admin/CustomFields', 'View/Edit ticketing custom fields' ],
+ 'Ticketing Global' => [ $fsurl.'rt/Admin/Global', 'View/Edit ticketing configuration applicable to all queues' ],
+ #"System Configuraiton"? useless, just makes people report errors about missing Module::Versions::Report #'Ticketing Tools' => [ $fsurl.'rt/Admin/Tools', '' ],
+;
+
tie my %config_dialup, 'Tie::IxHash',
- 'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ],
+ 'Access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ],
;
tie my %config_broadband, 'Tie::IxHash',
- 'View/Edit routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
- 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
+ 'Routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
+ 'Address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
+;
+
+tie my %config_phone, 'Tie::IxHash',
+ 'View/Edit phone device types' => [ $fsurl.'browse/part_device.html', 'Phone device types' ],
;
tie my %config_misc, 'Tie::IxHash';
-$config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service. Tracked for informational purposes' ]
+$config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service. Tracked for informational purposes' ]
if $curuser->access_right('Edit advertising sources')
|| $curuser->access_right('Edit global advertising sources');
if ( $curuser->access_right('Configuration') ) {
- $config_misc{'View/Edit virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ];
- $config_misc{'View/Edit message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ];
- $config_misc{'View/Edit inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ];
+ $config_misc{'Virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ];
+ $config_misc{'Message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ];
+ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ];
}
tie my %config_menu, 'Tie::IxHash';
@@ -331,8 +429,9 @@ if ( $curuser->access_right('Configuration' ) ) {
'Employees' => [ \%config_employees, '' ],
);
}
-$config_menu{'Provisioning, services and packages'} =
- [ \%config_export_svc_pkg, '' ]
+$config_menu{'Provisioning and services'} = [ \%config_export_svc, '' ]
+ if $curuser->access_right('Configuration' );
+$config_menu{'Packages'} = [ \%config_pkg, '' ]
if $curuser->access_right('Configuration' )
|| $curuser->access_right('Edit package definitions')
|| $curuser->access_right('Edit global package definitions');
@@ -341,10 +440,15 @@ $config_menu{'Resellers'} = [ \%config_agent, '' ]
$config_menu{'Billing'} = [ \%config_billing, '' ]
if $curuser->access_right('Edit billing events')
|| $curuser->access_right('Edit global billing events');
+$config_menu{'Ticketing'} = [ \%config_ticketing, '' ]
+ if $conf->config('ticket_system')
+ && FS::TicketSystem->access_right(\%session, 'ShowConfigTab');
$config_menu{'Dialup'} = [ \%config_dialup, '' ]
if ( $curuser->access_right('Dialup configuration') );
$config_menu{'Fixed (username-less) broadband'} = [ \%config_broadband, '' ]
if ( $curuser->access_right('Broadband configuration') );
+$config_menu{'Phone'} = [ \%config_phone, '' ]
+ if ( $curuser->access_right('Configuration') );
$config_menu{'Miscellaneous'} = [ \%config_misc, '' ]
if $curuser->access_right('Edit advertising sources')
|| $curuser->access_right('Edit global advertising sources');
@@ -362,6 +466,8 @@ if ( $conf->config('ticket_system') ) {
'Ticketing start page',
],
}
+$menu{'New customer'} = [ $fsurl.'edit/cust_main.cgi', 'Add a new customer' ]
+ if $curuser->access_right('New customer');
$menu{'Reports'} = [ \%report_menu, 'Lists, reporting and graphing' ]
if keys %report_menu;
$menu{'Tools'} = [ \%tools_menu, 'Tools' ]
@@ -374,6 +480,7 @@ $menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ]
|| $curuser->access_right('Edit global billing events')
|| $curuser->access_right('Dialup configuration')
|| $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Phone configuration')
|| $curuser->access_right('Edit advertising sources')
|| $curuser->access_right('Edit global advertising sources');
@@ -417,7 +524,7 @@ sub submenu {
} keys %$submenu )
). "\n".
- "myMenu$menunum.width = 280;\n",
+ "myMenu$menunum.width = 256;\n",
"myMenu$menunum";
diff --git a/httemplate/elements/menuarrow.gif b/httemplate/elements/menuarrow.gif
new file mode 100644
index 0000000..ed2dee0
--- /dev/null
+++ b/httemplate/elements/menuarrow.gif
Binary files differ
diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html
index ec6c13f..e6b7fb1 100644
--- a/httemplate/elements/menubar.html
+++ b/httemplate/elements/menubar.html
@@ -1,10 +1,108 @@
-%
-% my($item, $url, @html);
-% while (@_) {
-% ($item, $url) = splice(@_,0,2);
-% next if $item =~ /^\s*Main\s+Menu\s*$/i;
-% push @html, qq!<A HREF="$url">$item</A>!;
-% }
-%
-
-<% join(' | ', @html) %>
+<%doc>
+
+Example:
+
+ include( '/elements/menubar.html',
+
+ #options hashref (optional)
+ { 'newstyle' => 1, #may become the default at some point
+ 'url_base' => '', #prepended to menubar URLs, for convenience
+ 'selected' => '', #currently selected label
+ },
+
+ #menubar entries (required)
+ 'label' => $url,
+ 'label2' => $url2,
+ #etc.
+
+ );
+
+</%doc>
+%if ( $opt->{'newstyle'} ) {
+
+% #false laziness w/header.html... shouldn't these just go in freeside.css?
+
+ <style type="text/css">
+ a.fsblackbutton {
+ background-color:#333333;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ text-decoration:none;
+ overflow:visible;
+ filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666')
+ }
+
+ a.fsblackbuttonselected {
+ background-color:#7e0079;
+ color: #ffffff;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+ /*font-weight:bold;*/
+ /*padding-left:12px;
+ padding-right:12px;*/
+ padding-left:4px;
+ padding-right:4px;
+ text-decoration:none;
+ overflow:visible;
+ filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079')
+ }
+ </style>
+
+ <TABLE BGCOLOR="#000000" BORDER=0 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TD><IMG SRC="<%$fsurl%>images/gray-black-side.png" WIDTH=13 HEIGHT=25></TD>
+ <TD>
+ <% join(' ', @html ) %>
+ </TD>
+ <TD><IMG SRC="<%$fsurl%>images/black-gray-side.png" WIDTH=13 HEIGHT=25></TD>
+ </TD>
+ </TR>
+ </TABLE>
+
+%} else {
+
+ <% join(' | ', @html) %>
+
+%}
+<%init>
+
+my $opt = ref($_[0]) ? shift : {};
+
+my $url_base = $opt->{'url_base'};
+
+my @html;
+while (@_) {
+
+ my ($item, $url) = splice(@_,0,2);
+ next if $item =~ /^\s*Main\s+Menu\s*$/i;
+
+ my $style = '';
+ if ( $opt->{'newstyle'} ) {
+
+ my $dclass = $item eq $opt->{'selected'}
+ ? 'fsblackbuttonselected'
+ : 'fsblackbutton';
+
+ $style =
+ qq( CLASS="$dclass" ).
+ qq( onMouseOver="this.className='fsblackbuttonselected'; return true;" ).
+ qq( onMouseOut="this.className='$dclass'; return true;" );
+ }
+
+ push @html, qq!<A HREF="$url_base$url" $style>$item</A>!;
+
+}
+
+</%init>
diff --git a/httemplate/elements/popup_link-cust_main.html b/httemplate/elements/popup_link-cust_main.html
index 6d92301..454fcc4 100644
--- a/httemplate/elements/popup_link-cust_main.html
+++ b/httemplate/elements/popup_link-cust_main.html
@@ -4,7 +4,7 @@ Example:
include('/elements/init_overlib.html')
- include( '/elements/cust_popup_link.html', { #hashref or a list, either way
+ include( '/elements/popup_link-cust_main.html', { #hashref or a list, either way
#required
'action' => 'content.html', # uri for content of popup which should
diff --git a/httemplate/elements/popup_link-ping.html b/httemplate/elements/popup_link-ping.html
new file mode 100644
index 0000000..9e5f143
--- /dev/null
+++ b/httemplate/elements/popup_link-ping.html
@@ -0,0 +1,30 @@
+<%doc>
+
+Example:
+
+ include('/elements/init_overlib.html')
+
+ include( '/elements/popup_link-ping.html', { #hashref or a list, either way
+ 'ip' => '10.9.8.7',
+ })
+
+</%doc>
+<% include('/elements/popup_link.html', $params ) %>\
+<%init>
+
+my $params = { 'closetext' => 'Close' };
+
+if (ref($_[0]) eq 'HASH') {
+ $params = { %$params, %{ $_[0] } };
+} else {
+ $params = { %$params, @_ };
+}
+
+$params->{'label'} ||= 'ping';
+$params->{'actionlabel'} ||= 'Ping '. $params->{'ip'};
+$params->{'width'} ||= 350;
+$params->{'height'} ||= 220;
+
+$params->{'action'} = $p. 'misc/ping.html?'. $params->{'ip'};
+
+</%init>
diff --git a/httemplate/elements/popup_link.html b/httemplate/elements/popup_link.html
index 2019387..49b624c 100644
--- a/httemplate/elements/popup_link.html
+++ b/httemplate/elements/popup_link.html
@@ -22,14 +22,16 @@ Example:
#uncommon opt
'aname' => "target", # link NAME= value, useful for #targets
'target' => '_parent',
+ 'style' => 'css-attribute:value',
} )
</%doc>
% if ($params->{'action'} && $label) {
<A HREF="javascript:void(0);"
- onClick="<% $onclick %>"
- <% $params->{'aname'} ? 'NAME="'. $params->{'aname'}. '"' : '' %>
- <% $params->{'target'} ? 'TARGET="'. $params->{'target'}. '"' : '' %>
+ onClick="<% $onclick |n %>"
+ <% $params->{'aname'} ? 'NAME="'. $params->{'aname'}. '"' : '' |n %>
+ <% $params->{'target'} ? 'TARGET="'. $params->{'target'}. '"' : '' |n %>
+ <% $params->{'style'} ? 'STYLE="'. $params->{'style'}. '"' : '' |n %>
><% $label %></A>\
% }
<%init>
diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html
index 0bd71ff..8a55efb 100644
--- a/httemplate/elements/progress-popup.html
+++ b/httemplate/elements/progress-popup.html
@@ -31,10 +31,12 @@ function updateStatus( status_statustext ) {
var statusArray = eval('(' + status_statustext + ')');
var status = statusArray[0];
var statustext = statusArray[1];
+ var actiontext = statusArray[2];
//if ( status == 'progress' ) {
//IE workaround, no i have no idea why
if ( status.indexOf('progress') > -1 ) {
+ document.getElementById("progress_message").innerHTML = actiontext + '...';
document.getElementById("progress_percent").innerHTML = statustext + '%';
bar1.set(statustext);
bar1.update;
diff --git a/httemplate/elements/select-county.html b/httemplate/elements/select-county.html
index 59f235a..aa88abe 100644
--- a/httemplate/elements/select-county.html
+++ b/httemplate/elements/select-county.html
@@ -58,10 +58,12 @@ Example:
if ( countiesArray.length > 1 ) {
what.form.<% $pre %>county.style.display = '';
- countyFormLabel.style.visibility = 'visible';
+ //countyFormLabel.style.visibility = 'visible';
+ countyFormLabel.style.display = '';
} else {
what.form.<% $pre %>county.style.display = 'none';
- countyFormLabel.style.visibility = 'hidden';
+ //countyFormLabel.style.visibility = 'hidden';
+ countyFormLabel.style.display = 'none';
}
//run the callback
diff --git a/httemplate/elements/select-cust-part_pkg.html b/httemplate/elements/select-cust-part_pkg.html
index 2926629..7f91e81 100644
--- a/httemplate/elements/select-cust-part_pkg.html
+++ b/httemplate/elements/select-cust-part_pkg.html
@@ -31,11 +31,11 @@ my( %opt ) = @_;
my $cust_main = $opt{'cust_main'}
or die "cust_main not specified";
-$opt{'extra_sql'} .=
- ' AND ( agentnum IS NOT NULL '.
- ' OR 0 < ( SELECT COUNT(*) FROM type_pkgs '.
- ' WHERE typenum = '. $cust_main->agent->typenum.
- ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'.
- ' )';
+$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent );
+# ' AND ( agentnum IS NOT NULL '.
+# ' OR 0 < ( SELECT COUNT(*) FROM type_pkgs '.
+# ' WHERE typenum = '. $cust_main->agent->typenum.
+# ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'.
+# ' )';
</%init>
diff --git a/httemplate/elements/select-cust-pkg_class.html b/httemplate/elements/select-cust-pkg_class.html
new file mode 100644
index 0000000..5c19efa
--- /dev/null
+++ b/httemplate/elements/select-cust-pkg_class.html
@@ -0,0 +1,12 @@
+<% include( '/elements/select-pkg_class.html',
+ 'pre_options' => [ '-1' => 'all' ], #XXX a config ?
+ #'pre_options' => [ '-2' => 'Select package class' ],
+ 'disable_empty' => 1,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/select-cust_main-status.html b/httemplate/elements/select-cust_main-status.html
index 2e0b6cb..bdbaac7 100644
--- a/httemplate/elements/select-cust_main-status.html
+++ b/httemplate/elements/select-cust_main-status.html
@@ -8,7 +8,9 @@
% foreach my $option ( @{ $opt{'statuses'} } ) {
<OPTION VALUE="<% $option %>"
- <% $option eq $curr_value ? 'SELECTED' : '' %>
+ <% ref($value) && $value->{$option} || $option eq $value
+ ? 'SELECTED' : ''
+ %>
><% $option %>
% }
@@ -25,6 +27,7 @@ my $onchange = $opt{'onchange'}
? 'onChange="'. $opt{'onchange'}. '(this)"'
: '';
-my $curr_value = $opt{'curr_value'} || $opt{'value'};
+my $value = $opt{'curr_value'} || $opt{'value'};
+$value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
</%init>
diff --git a/httemplate/elements/select-cust_pkg-balances.html b/httemplate/elements/select-cust_pkg-balances.html
new file mode 100644
index 0000000..cd2e1a8
--- /dev/null
+++ b/httemplate/elements/select-cust_pkg-balances.html
@@ -0,0 +1,32 @@
+<SELECT NAME="pkgnum">
+ <OPTION VALUE="">(any)
+% foreach my $cust_pkg (@cust_pkg) {
+% my $sel = ( $cgi->param('pkgnum') == $cust_pkg->pkgnum ) ? 'SELECTED' : '';
+ <OPTION <% $sel %> VALUE="<% $cust_pkg->pkgnum %>"><% $cust_pkg->pkg_label_long |h %>
+% }
+</SELECT>
+<%init>
+
+my %opt = @_;
+
+my $cgi = $opt{'cgi'};
+
+my @cust_pkg;
+if ( $opt{'cust_pkg'} ) {
+
+ @cust_pkg = @{ $opt{'cust_pkg'} };
+
+} else {
+
+ my $custnum = $opt{'custnum'};
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum\n";
+
+ @cust_pkg =
+ grep { ! $_->get('cancel') || $cust_main->balance_pkgnum($_->pkgnum) }
+ $cust_main->all_pkgs;
+
+}
+
+</%init>
diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html
index 2d545c0..ec37eaf 100644
--- a/httemplate/elements/select-cust_pkg-status.html
+++ b/httemplate/elements/select-cust_pkg-status.html
@@ -8,7 +8,9 @@
% foreach my $option ( @{ $opt{'statuses'} } ) {
<OPTION VALUE="<% $option %>"
- <% $option eq $curr_value ? 'SELECTED' : '' %>
+ <% ref($value) && $value->{$option} || $option eq $value
+ ? 'SELECTED' : ''
+ %>
><% $option %>
% }
@@ -25,6 +27,7 @@ my $onchange = $opt{'onchange'}
? 'onChange="'. $opt{'onchange'}. '(this)"'
: '';
-my $curr_value = $opt{'curr_value'} || $opt{'value'};
+my $value = $opt{'curr_value'} || $opt{'value'};
+$value = [ split(/\s*,\s*/, $value) ] if $opt{'multiple'} && $value =~ /,/;
</%init>
diff --git a/httemplate/elements/select-did.html b/httemplate/elements/select-did.html
index 0695164..af8d595 100644
--- a/httemplate/elements/select-did.html
+++ b/httemplate/elements/select-did.html
@@ -68,8 +68,11 @@ my %opt = @_;
my $conf = new FS::Conf;
my $country = $conf->config('countrydefault') || 'US';
+#false laziness w/tr-select-did.html
#XXX make sure this comes through on errors too
-my $svcpart = $opt{'svcpart'} || $opt{'object'}->svcpart;
+my $svcpart = $opt{'svcpart'}
+ || $opt{'object'}->svcpart
+ || $opt{'object'}->cust_svc->svcpart;
my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
die "unknown svcpart $svcpart" unless $part_svc;
diff --git a/httemplate/elements/select-domain.html b/httemplate/elements/select-domain.html
index a9998da..3372e06 100644
--- a/httemplate/elements/select-domain.html
+++ b/httemplate/elements/select-domain.html
@@ -7,7 +7,7 @@
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
' LEFT JOIN cust_main USING ( custnum ) ',
'agent_virt' => 1,
- 'agent_null-right' => 'View/link unlinked services',
+ 'agent_null_right' => 'View/link unlinked services',
@_,
)
%>
diff --git a/httemplate/elements/select-part_pkg.html b/httemplate/elements/select-part_pkg.html
index 52b1cca..6b697ab 100644
--- a/httemplate/elements/select-part_pkg.html
+++ b/httemplate/elements/select-part_pkg.html
@@ -22,17 +22,27 @@ Example:
'name_col' => 'pkg',
'empty_label' => 'Select package', #should this be the default?
'label_callback' => sub { shift->pkg_comment },
- 'hashref' => { 'disabled' => '' },
+ 'hashref' => \%hash,
%opt,
)
%>
<%init>
-
+
my( %opt ) = @_;
$opt{'records'} = delete $opt{'part_pkg'}
if $opt{'part_pkg'};
+my %hash = ( 'disabled' => '' );
+
+if ( exists($opt{'classnum'}) && defined($opt{'classnum'}) ) {
+ if ( $opt{'classnum'} > 0 ) {
+ $hash{'classnum'} = $opt{'classnum'};
+ } elsif ( $opt{'classnum'} eq '' || $opt{'classnum'} == 0 ) {
+ $hash{'classnum'} = '';
+ } #else -1 or not specified, all classes, so don't set classnum
+}
+
$opt{'extra_sql'} .= ' AND '. FS::part_pkg->curuser_pkgs_sql;
</%init>
diff --git a/httemplate/elements/select-part_svc.html b/httemplate/elements/select-part_svc.html
new file mode 100644
index 0000000..72ab7f6
--- /dev/null
+++ b/httemplate/elements/select-part_svc.html
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'part_svc',
+ 'name_col' => 'svc',
+ 'label_showkey' => 1,
+ #N/A 'empty_label' => '(none)',
+ %opt,
+ )
+%>
+<%init>
+
+my( %opt ) = @_;
+
+$opt{'records'} = delete $opt{'part_svc'}
+ if $opt{'part_svc'};
+
+$opt{'records'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } )
+
+</%init>
diff --git a/httemplate/elements/select-svc_acct-domain.html b/httemplate/elements/select-svc_acct-domain.html
new file mode 100644
index 0000000..c9a9206
--- /dev/null
+++ b/httemplate/elements/select-svc_acct-domain.html
@@ -0,0 +1,46 @@
+<SELECT NAME="domsvc" SIZE=1>
+% foreach my $svcnum (
+% sort { $svc_domain{$a} cmp $svc_domain{$b} }
+% keys %svc_domain
+% ) {
+% my $svc_domain = $svc_domain{$svcnum};
+% my $selected = ($svcnum == $domsvc) ? ' SELECTED' : ''
+
+ <OPTION VALUE="<% $svcnum %>" <% $selected %>><% $svc_domain{$svcnum} %>
+
+% }
+
+</SELECT>
+<%init>
+
+my %opt = @_;
+
+my $domsvc = $opt{'curr_value'};
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+my $pkgnum = $cust_pkg ? $cust_pkg->pkgnum : '';
+
+my %svc_domain = ();
+
+if ( $domsvc ) {
+ my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc } );
+ if ( $svc_domain ) {
+ $svc_domain{$svc_domain->svcnum} = $svc_domain;
+ } else {
+ warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc";
+ }
+}
+
+%svc_domain = (
+ %svc_domain,
+ FS::svc_acct->domain_select_hash( 'svcpart' => $part_svc->svcpart,
+ 'pkgnum' => $pkgnum,
+ )
+);
+</%init>
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
index 4efbcba..10a8b27 100644
--- a/httemplate/elements/select-table.html
+++ b/httemplate/elements/select-table.html
@@ -34,6 +34,7 @@ Example:
'empty_label' => '', #better specify it though, the default might change
'multiple' => 0, # bool
'disable_empty' => 0, # bool (implied by multiple)
+ 'label_showkey' => 0, # bool
'label_callback' => sub { my $record = shift; return "label"; },
#more params controlling HTML stuff about the <SELECT>
@@ -64,21 +65,32 @@ Example:
>
% while ( @pre_options ) {
- <OPTION VALUE="<% shift(@pre_options) %>"><% shift(@pre_options) %>
-
+% my $pre_opt = shift(@pre_options);
+% my $pre_label = shift(@pre_options);
+% my $selected = ( ref($value) && $value->{$pre_opt} )
+% || ( $value eq $pre_opt );
+ <OPTION VALUE="<% $pre_opt %>"
+ <% $selected ? 'SELECTED' : '' %>
+ ><% $pre_label %>
% }
% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
<OPTION VALUE=""><% $opt{'empty_label'} || 'all' %>
% }
-% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col() } @records ) {
+% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col()
+% || $a->$key() <=> $b->$key()
+% }
+% @records
+% )
+% {
% my $recvalue = $record->$key();
<OPTION VALUE="<% $recvalue %>"
<% ref($value) && $value->{$recvalue} || $value == $recvalue
? ' SELECTED' : ''
%>
- ><% $opt{'label_callback'}
+ ><% $opt{'label_showkey'} ? "$recvalue: " : '' %>
+ <% $opt{'label_callback'}
? &{ $opt{'label_callback'} }( $record )
: $record->$name_col()
%>
@@ -139,7 +151,7 @@ if ( $opt{'records'} ) {
});
}
-unless ( ! $value
+unless ( $value < 1 # !$value #ignore negatives too
or ref($value)
or ! exists( $opt{hashref}->{disabled} ) #??
or grep { $value == $_->$key() } @records
diff --git a/httemplate/elements/select-taxclass.html b/httemplate/elements/select-taxclass.html
index 2504a5b..6845d23 100644
--- a/httemplate/elements/select-taxclass.html
+++ b/httemplate/elements/select-taxclass.html
@@ -1,6 +1,6 @@
% if ( $conf->exists('enable_taxclasses') ) {
- <SELECT NAME="<% $opt{'name'} || 'taxclass' %>">
+ <SELECT NAME="<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>">
% if ( $conf->exists('require_taxclasses') ) {
<OPTION VALUE="(select)">Select tax class
@@ -16,7 +16,7 @@
% } else {
- <INPUT TYPE="hidden" NAME="taxclass" VALUE="<% $selected_taxclass %>">
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>" VALUE="<% $selected_taxclass %>">
% }
@@ -30,9 +30,9 @@ my $conf = new FS::Conf;
unless ( $opt{'taxclasses'} ) {
#my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
- my $sth = dbh->prepare('SELECT taxclass FROM part_pkg_taxclass')
+ my $sth = dbh->prepare("SELECT taxclass FROM part_pkg_taxclass WHERE disabled IS NULL OR disabled = '' OR taxclass = ?")
or die dbh->errstr;
- $sth->execute or die $sth->errstr;
+ $sth->execute($selected_taxclass) or die $sth->errstr;
my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
@{ $opt{'taxclasses'} } = grep $_, keys %taxclasses;
diff --git a/httemplate/elements/select-terms.html b/httemplate/elements/select-terms.html
new file mode 100644
index 0000000..629d1e4
--- /dev/null
+++ b/httemplate/elements/select-terms.html
@@ -0,0 +1,26 @@
+<SELECT NAME = "invoice_terms"
+ ID = "invoice_terms"
+ <% $opt{'disabled'} ? 'DISABLED' : ''%>
+>
+ <OPTION VALUE=""><% $empty_label %>
+% foreach my $term ( @terms ) {
+ <OPTION VALUE="<% $term %>" <% $curr_value eq $term ? ' SELECTED' : '' %>><% $term %>
+% }
+</SELECT>
+<%init>
+
+my %opt = @_;
+my $curr_value = $opt{'curr_value'};
+my $conf = new FS::Conf;
+
+my $empty_label =
+ $opt{'empty_label'}
+ || 'Default ('.
+ ($conf->config('invoice_default_terms') || 'Payable upon receipt').
+ ')';
+
+my @terms = ( 'Payable upon receipt',
+ ( map "Net $_", 0, 10, 15, 20, 30, 45, 60 ),
+ );
+
+</%init>
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
index 82f5dd1..89fe41b 100644
--- a/httemplate/elements/selectlayers.html
+++ b/httemplate/elements/selectlayers.html
@@ -14,7 +14,7 @@ Example:
#XXX put this handling it its own selectlayers-fields.html element?
'layer_prefix' => 'prefix_', #optional prefix for fieldnames
- 'layer_fields' => [ 'layer' => [ 'fieldname',
+ 'layer_fields' => { 'layer' => [ 'fieldname',
{ label => 'fieldname2',
type => 'text', #implemented:
# text, money, fixed,
@@ -23,6 +23,7 @@ Example:
# select, select-agent,
# select-pkg_class,
# select-part_referral,
+ # select-taxclass,
# select-table,
#XXX tbd:
# more?
@@ -32,6 +33,7 @@ Example:
'layer2' => [ 'l2fieldname',
...
],
+ },
#current values for layer fields above
'layer_values' => { 'layer' => { 'fieldname' => 'current_value',
@@ -63,18 +65,29 @@ Example:
<SCRIPT TYPE="text/javascript">
% }
% unless ( grep $opt{$_}, qw(html_only select_only layers_only) ) {
- //alert('start function define');
+
+% if ( $opt{layermap} ) {
+% my %map = %{ $opt{layermap} };
+ var layermap = { "":"",
+ <% join(',', map { qq("$_":"$map{$_}") } keys %map ) %>
+ };
+% }
+
function <% $key %>changed(what) {
<% $opt{'onchange'} %>
var <% $key %>layer = what.options[what.selectedIndex].value;
-% foreach my $layer ( keys %$options ) {
-
+% foreach my $layer ( @layers ) {
+%
+% if ( $opt{layermap} ) {
+ if ( layermap[ <% $key %>layer ] == "<% $layer %>" ) {
+% } else {
if (<% $key %>layer == "<% $layer %>" ) {
+% }
-% foreach my $not ( grep { $_ ne $layer } keys %$options ) {
+% foreach my $not ( grep { $_ ne $layer } @layers ) {
% my $element = "document.getElementById('${key}d$not').style";
<% $element %>.display = "none";
<% $element %>.zIndex = 0;
@@ -90,7 +103,6 @@ Example:
//<% $opt{'onchange'} %>
}
- //alert('end function define');
% }
% unless ( grep $opt{$_}, qw(html_only js_only select_only layers_only) ) {
</SCRIPT>
@@ -124,10 +136,16 @@ Example:
%
% unless ( grep $opt{$_}, qw(js_only select_only) ) {
-% foreach my $layer ( keys %$options ) {
+% foreach my $layer ( @layers ) {
+% my $selected_layer;
+% if ( $opt{layermap} ) {
+% $selected_layer = $opt{layermap}->{$selected};
+% } else {
+% $selected_layer = $selected;
+% }
<DIV ID="<% $key %>d<% $layer %>"
- STYLE="<% $layer eq $selected
+ STYLE="<% $selected_layer eq $layer
? 'display: "" ; z-index: 1'
: 'display: none; z-index: 0'
%>"
@@ -162,6 +180,14 @@ tie my %options, 'Tie::IxHash',
my $between = exists($opt{html_between}) ? $opt{html_between} : '';
my $options = \%options;
+my @layers = ();
+if ( $opt{layermap} ) {
+ my %layers = map { $opt{layermap}->{$_} => 1 } keys %options;
+ @layers = keys %layers;
+} else {
+ @layers = keys %options;
+}
+
my $selected = exists($opt{curr_value}) ? $opt{curr_value} : '';
#XXX eek. also eek $layer_fields in the layer_callback() call...
diff --git a/httemplate/elements/tr-checkbox.html b/httemplate/elements/tr-checkbox.html
index 2e6d1f1..c3cf92d 100644
--- a/httemplate/elements/tr-checkbox.html
+++ b/httemplate/elements/tr-checkbox.html
@@ -1,13 +1,7 @@
<% include('tr-td-label.html', @_ ) %>
<TD <% $style %>>
- <INPUT TYPE = "checkbox"
- NAME = "<% $opt{field} %>"
- ID = "<% $opt{id} %>"
- VALUE = "<% $opt{value} %>"
- <% $opt{curr_value} eq $opt{value} ? ' CHECKED' : '' %>
- <% $onchange %>
- >
+ <% include('checkbox.html', @_) %>
</TD>
</TR>
diff --git a/httemplate/elements/tr-input-date-field.html b/httemplate/elements/tr-input-date-field.html
index 11581d5..2a731e1 100644
--- a/httemplate/elements/tr-input-date-field.html
+++ b/httemplate/elements/tr-input-date-field.html
@@ -23,17 +23,30 @@
<%init>
-my($name, $value, $label, $format, $usedatetime) = @_;
+my($name, $value, $label, $format, $usedatetime);
+if ( ref($_[0]) ) {
+ my $opt = shift;
+ $name = $opt->{'name'};
+ $value = $opt->{'value'};
+ $label = $opt->{'label'};
+ $format = $opt->{'format'};
+ $usedatetime = $opt->{'usedatetime'};
+} else {
+ ($name, $value, $label, $format, $usedatetime) = @_;
+}
$format = "%m/%d/%Y" unless $format;
$label = $name unless $label;
-if ($usedatetime) {
- my $dt = DateTime->from_epoch(epoch => $value, time_zone => 'floating');
- $value = $dt->strftime($format)
- unless $value eq '';
-}else{
- $value = time2str($format, $value);
+if ( $value =~ /\S/ ) {
+ if ( $usedatetime ) {
+ my $dt = DateTime->from_epoch(epoch => $value, time_zone => 'floating');
+ $value = $dt->strftime($format);
+ } elsif ( $value =~ /^\d+$/ ) {
+ $value = time2str($format, $value);
+ }
+} else {
+ $value = '';
}
</%init>
diff --git a/httemplate/elements/tr-justtitle.html b/httemplate/elements/tr-justtitle.html
index 7839a8c..8c14d34 100644
--- a/httemplate/elements/tr-justtitle.html
+++ b/httemplate/elements/tr-justtitle.html
@@ -1,5 +1,5 @@
<TR>
- <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <TH BGCOLOR="#e8e8e8" COLSPAN=<% $opt{colspan} || 2 %> ALIGN="left">
<FONT SIZE="+1"><% $opt{value} %></FONT>
</TH>
</TR>
diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html
new file mode 100644
index 0000000..75f1f6f
--- /dev/null
+++ b/httemplate/elements/tr-select-cust-part_pkg.html
@@ -0,0 +1,107 @@
+%if ( scalar(@pkg_class) > 1 && ! $conf->exists('disable-cust-pkg_class') ) {
+
+ <% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/cust-part_pkg.cgi',
+ 'subs' => [ 'get_part_pkg' ],
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function classnum_changed(what) {
+
+ what.form.pkgpart.disabled = 'disabled'; //disable part_pkg dropdown
+ what.form.submit.disabled = true; //disable the submit button
+
+ classnum = what.options[what.selectedIndex].value;
+
+ function update_part_pkg(part_pkg) {
+
+ // blank the current packages
+ for ( var i = what.form.pkgpart.length; i>= 0; i-- )
+ what.form.pkgpart.options[i] = null;
+
+ // add the new packages
+ opt(what.form.pkgpart, '', 'Select package');
+ var packagesArray = eval('(' + part_pkg + ')' );
+ for ( var s = 0; s < packagesArray.length; s=s+2 ) {
+ var packagesLabel = packagesArray[s+1];
+ opt(what.form.pkgpart, packagesArray[s], packagesLabel);
+ }
+
+ what.form.pkgpart.disabled = ''; //re-enable part_pkg dropdown
+
+ }
+
+ get_part_pkg( <% $cust_main->custnum %>, classnum, update_part_pkg );
+
+ }
+
+ </SCRIPT>
+
+ <TR>
+ <TH ALIGN="right">Package Class</TH>
+ <TD COLSPAN=7>
+ <% include('/elements/select-cust-pkg_class.html',
+ 'curr_value' => $opt{'classnum'},
+ 'pkg_class' => \@pkg_class,
+ 'onchange' => 'classnum_changed',
+ )
+ %>
+ </TD>
+ </TR>
+
+%}
+
+<TR>
+ <TH ALIGN="right">Package</TH>
+ <TD COLSPAN=7>
+ <% include('/elements/select-cust-part_pkg.html',
+ 'curr_value' => $opt{'curr_value'}, #$pkgpart
+ 'classnum' => $opt{'classnum'},
+ 'cust_main' => $opt{'cust_main'}, #$cust_main
+ 'onchange' => 'enable_order_pkg',
+ )
+ %>
+ </TD>
+</TR>
+
+<%init>
+
+my $conf = new FS::Conf;
+
+my %opt = @_;
+
+my $pre_label = $opt{'pre_label'} || '';
+$pre_label .= ' ' if length($pre_label) && $pre_label =~ /\S$/;
+
+my $cust_main = $opt{'cust_main'}
+ or die "cust_main not specified";
+
+#my @pkg_class = sort { $a->classname cmp $b->classname }
+# qsearch( 'pkg_class', { 'disabled' => '' } );
+
+#"normal" part_pkg agent virtualization (agentnum or type)
+my @part_pkg = qsearch({
+ 'select' => 'DISTINCT classnum',
+ 'table' => 'part_pkg',
+ 'hashref' => { 'disabled' => '' },
+ 'extra_sql' =>
+ ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $opt{'cust_main'}->agent ),
+});
+
+my @pkg_class =
+ sort { $a->classname cmp $b->classname } #should get a sort order in config
+ map { $_->pkg_class || new FS::pkg_class { 'classnum' => '',
+ 'classname' => '(none)' }
+ }
+ @part_pkg;
+
+</%init>
diff --git a/httemplate/elements/tr-select-cust_pkg-balances.html b/httemplate/elements/tr-select-cust_pkg-balances.html
new file mode 100644
index 0000000..89dc5d4
--- /dev/null
+++ b/httemplate/elements/tr-select-cust_pkg-balances.html
@@ -0,0 +1,31 @@
+% if ( scalar(@cust_pkg) == 0 ) {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="">
+% } elsif ( scalar(@cust_pkg) == 1 ) {
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $cust_pkg[0]->pkgnum %>">
+% } else {
+ <TR>
+ <TD ALIGN="right">For package</TD>
+ <TD COLSPAN=2>
+ <% include('select-cust_pkg-balances.html',
+ 'cust_pkg' => \@cust_pkg,
+ 'cgi' => $opt{'cgi'},
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+my %opt = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum\n";
+
+my @cust_pkg =
+ grep { ! $_->get('cancel') || $cust_main->balance_pkgnum($_->pkgnum) }
+ $cust_main->all_pkgs;
+
+</%init>
diff --git a/httemplate/elements/tr-select-did.html b/httemplate/elements/tr-select-did.html
index c784033..987ade6 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 '' ) {
+% if ( $opt{'curr_value'} ne '' && $use_selector ) {
<TD BGCOLOR="#dddddd" <% $cell_style %>><% $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'} |h %></TD>
@@ -19,7 +19,23 @@
<%init>
my %opt = @_;
-
+#warn Dumper(\%opt); if $DEBUG;
my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+#false laziness w/select-did.html
+#XXX make sure this comes through on errors too
+my $svcpart = $opt{'svcpart'}
+ || $opt{'object'}->svcpart
+ || $opt{'object'}->cust_svc->svcpart;
+
+my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } );
+die "unknown svcpart $svcpart" unless $part_svc;
+
+my @exports = $part_svc->part_export_did;
+if ( scalar(@exports) > 1 ) {
+ die "more than one DID-providing export attached to svcpart $svcpart";
+}
+
+my $use_selector = scalar(@exports) ? 1 : 0;
+
</%init>
diff --git a/httemplate/elements/tr-select-part_svc.html b/httemplate/elements/tr-select-part_svc.html
index 0274ef1..af51487 100644
--- a/httemplate/elements/tr-select-part_svc.html
+++ b/httemplate/elements/tr-select-part_svc.html
@@ -7,11 +7,8 @@
<TR>
<TD ALIGN="right"><% $opt{'label'} || 'Package definition' %></TD>
<TD>
- <% include( '/elements/select-table.html',
- 'table' => 'part_svc',
- 'name_col' => 'svc',
- 'multiple' => 1,
- #N/A 'empty_label' => '(none)',
+ <% include( '/elements/select-part_svc.html',
+ 'multiple' => 1,
%opt,
)
%>
diff --git a/httemplate/elements/tr-select-pkg_class.html b/httemplate/elements/tr-select-pkg_class.html
index aa27609..ece4b58 100644
--- a/httemplate/elements/tr-select-pkg_class.html
+++ b/httemplate/elements/tr-select-pkg_class.html
@@ -1,6 +1,6 @@
-% if ( scalar(@{ $opt{'pkg_class'} }) == 0 ) {
+% if ( $count == 0 ) {
- <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'classnum' %>" VALUE="">
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
% } else {
@@ -22,6 +22,6 @@
my %opt = @_;
my $classnum = $opt{'curr_value'} || $opt{'value'};
-$opt{'pkg_class'} ||= [ qsearch( 'pkg_class', {} ) ]; # { disabled=>'' } )
+my $count = scalar( qsearch( 'pkg_class', {} ) );
</%init>
diff --git a/httemplate/elements/tr-select-svc_acct-domain.html b/httemplate/elements/tr-select-svc_acct-domain.html
new file mode 100644
index 0000000..9d1a4b6
--- /dev/null
+++ b/httemplate/elements/tr-select-svc_acct-domain.html
@@ -0,0 +1,34 @@
+%if ( $columnflag eq 'F' ) {
+ <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>">
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Domain' %></TD>
+ <TD>
+ <% include('/elements/select-svc_acct-domain.html',
+ 'curr_value' => $domsvc,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+<%init>
+
+my %opt = @_;
+
+my $domsvc = $opt{'curr_value'};
+
+#required
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+my $columnflag = $part_svc->part_svc_column('domsvc')->columnflag;
+
+#optional
+my $cust_pkg = $opt{'cust_pkg'};
+$cust_pkg ||= qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+ if $opt{'pkgnum'};
+
+</%init>
diff --git a/httemplate/elements/tr-select-taxclass.html b/httemplate/elements/tr-select-taxclass.html
index 981c1a5..97f3cad 100644
--- a/httemplate/elements/tr-select-taxclass.html
+++ b/httemplate/elements/tr-select-taxclass.html
@@ -9,7 +9,11 @@
<TR>
<TD ALIGN="right"><% $opt{'label'} || 'Tax class: ' %></TD>
<TD>
- <% include( '/elements/select-taxclass.html', 'curr_value' => $selected_taxclass, %opt ) %>
+ <% include( '/elements/select-taxclass.html',
+ 'curr_value' => $selected_taxclass,
+ %opt
+ )
+ %>
</TD>
</TR>
@@ -23,9 +27,9 @@ my $selected_taxclass = $opt{'curr_value'}; # || $opt{'value'} necessary?
unless ( $opt{'taxclasses'} ) {
#my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
- my $sth = dbh->prepare('SELECT taxclass FROM part_pkg_taxclass')
+ my $sth = dbh->prepare("SELECT taxclass FROM part_pkg_taxclass WHERE disabled IS NULL OR disabled = '' OR taxclass = ?")
or die dbh->errstr;
- $sth->execute or die $sth->errstr;
+ $sth->execute($selected_taxclass) or die $sth->errstr;
my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
@{ $opt{'taxclasses'} } = grep $_, keys %taxclasses;
diff --git a/httemplate/elements/tr-selectmultiple-part_pkg.html b/httemplate/elements/tr-selectmultiple-part_pkg.html
index 455038d..d959a5b 100644
--- a/httemplate/elements/tr-selectmultiple-part_pkg.html
+++ b/httemplate/elements/tr-selectmultiple-part_pkg.html
@@ -2,11 +2,10 @@
<TD ALIGN="right"><% $opt{'label'} || 'Packages' %></TD>
<TD>
<% include( '/elements/select-table.html',
- 'table' => 'part_pkg',
- 'name_col' => 'pkg',
- 'value' => '',
- 'empty_label' => '(none)',
- 'element_etc' => 'multiple',
+ 'table' => 'part_pkg',
+ 'name_col' => 'pkg',
+ 'disable_empty' => 1,
+ 'element_etc' => 'multiple',
%opt,
)
%>
diff --git a/httemplate/elements/tr-textarea.html b/httemplate/elements/tr-textarea.html
new file mode 100644
index 0000000..ae2ef81
--- /dev/null
+++ b/httemplate/elements/tr-textarea.html
@@ -0,0 +1,30 @@
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $cell_style %>>
+
+ <TEXTAREA NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ <% $rows %>
+ <% $cols %>
+ <% $onchange %>
+ ><% $curr_value |h %></TEXTAREA>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $rows = $opt{'rows'} ? 'ROWS="'.$opt{'rows'}.'"' : '';
+my $cols = $opt{'cols'} ? 'COLS="'.$opt{'cols'}.'"' : '';
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+my $curr_value = $opt{'curr_value'};
+
+</%init>
diff --git a/httemplate/elements/tr-title.html b/httemplate/elements/tr-title.html
index 8517737..f2d269e 100644
--- a/httemplate/elements/tr-title.html
+++ b/httemplate/elements/tr-title.html
@@ -1,5 +1,10 @@
<TR>
- <TD BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TD>
+ <TD BGCOLOR="#e8e8e8" COLSPAN=<% $opt{colspan} || 2 %>>&nbsp;</TD>
</TR>
<% include('tr-justtitle.html', @_) %>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css
index 97c7da8..33ad90c 100644
--- a/httemplate/elements/xmenu.css
+++ b/httemplate/elements/xmenu.css
@@ -128,6 +128,8 @@
padding: 1px 5px 1px 5px;
+ font-size: 14px;
+
/* color: black; */
color: white;
text-decoration: none;
diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css
index 7591703..e86e4a6 100644
--- a/httemplate/elements/xmenu.top.css
+++ b/httemplate/elements/xmenu.top.css
@@ -125,6 +125,8 @@
padding: 1px 5px 1px 5px;
+ font-size: 16px;
+
/* color: black; */
color: white;
text-decoration: none;
diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi
index d7cae80..832660f 100644
--- a/httemplate/graph/cust_bill_pkg.cgi
+++ b/httemplate/graph/cust_bill_pkg.cgi
@@ -9,7 +9,7 @@
'links' => \@links,
'remove_empty' => 1,
'bottom_total' => 1,
- 'bottom_link' => "$link;",
+ 'bottom_link' => $bottom_link,
'agentnum' => $agentnum,
)
%>
@@ -18,34 +18,62 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1";
+my $bottom_link = "$link;";
+
#XXX or virtual
my( $agentnum, $sel_agent ) = ('', '');
if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$agentnum = $1;
+ $bottom_link .= "agentnum=$agentnum;";
$sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
die "agentnum $agentnum not found!" unless $sel_agent;
}
my $title = $sel_agent ? $sel_agent->agent.' ' : '';
-#false lazinessish w/search/cust_pkg.cgi
+#classnum (here)
+# 0: all classes
+# not specified: empty class
+# N: classnum
+#classnum (link)
+# not specified: all classes
+# 0: empty class
+# N: classnum
+
+#false lazinessish w/FS::cust_pkg::search_sql (previously search/cust_pkg.cgi)
my $classnum = 0;
my @pkg_class = ();
if ( $cgi->param('classnum') =~ /^(\d*)$/ ) {
$classnum = $1;
- if ( $classnum ) {
+
+ if ( $classnum ) { #a specific class
+
@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
die "classnum $classnum not found!" unless $pkg_class[0];
$title .= $pkg_class[0]->classname.' ';
- } elsif ( $classnum eq '' ) {
+ $bottom_link .= "classnum=$classnum;";
+
+ } elsif ( $classnum eq '' ) { #the empty class
+
$title .= 'Empty class ';
@pkg_class = ( '(empty class)' );
- } elsif ( $classnum eq '0' ) {
+ $bottom_link .= "classnum=0;";
+
+ } elsif ( $classnum eq '0' ) { #all classes
+
@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } );
push @pkg_class, '(empty class)';
+
}
}
#eslaf
+my $use_override = 0;
+$use_override = 1 if ( $cgi->param('use_override') );
+
+my $use_usage = 0;
+$use_usage = 1 if ( $cgi->param('use_usage') );
+
my $hue = 0;
#my $hue_increment = 170;
#my $hue_increment = 145;
@@ -57,8 +85,6 @@ my @labels = ();
my @colors = ();
my @links = ();
-my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1";
-
foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
my $col_scheme = Color::Scheme->new
@@ -69,34 +95,40 @@ foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
my @onetime_colors = ();
### fixup the color handling for package classes...
+ ### and usage
my $n = 0;
foreach my $pkg_class ( @pkg_class ) {
-
- push @items, 'cust_bill_pkg';
-
-
- push @labels,
- ( $sel_agent ? '' : $agent->agent.' ' ).
- ( $classnum eq '0'
- ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class )
- : ''
- );
-
- my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
- my $row_agentnum = $agent->agentnum;
- push @params, [ 'classnum' => $row_classnum,
- 'agentnum' => $row_agentnum,
- ];
-
- push @links, "$link;agentnum=$row_agentnum;classnum=$row_classnum;";
-
- @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9]
- unless @recur_colors;
- @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11]
- unless @onetime_colors;
- push @colors, shift @recur_colors;
-
+ foreach my $component ( $use_usage ? ('recurring', 'usage') : ('') ) {
+
+ push @items, 'cust_bill_pkg';
+
+ push @labels,
+ ( $sel_agent ? '' : $agent->agent.' ' ).
+ ( $classnum eq '0'
+ ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class )
+ : ''
+ ).
+ " $component";
+
+ my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
+ my $row_agentnum = $agent->agentnum;
+ push @params, [ 'classnum' => $row_classnum,
+ 'agentnum' => $row_agentnum,
+ 'use_override' => $use_override,
+ 'use_usage' => $component,
+ ];
+
+ push @links, "$link;agentnum=$row_agentnum;classnum=$row_classnum;".
+ "use_override=$use_override;use_usage=$component;";
+
+ @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9]
+ unless @recur_colors;
+ @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11]
+ unless @onetime_colors;
+ push @colors, shift @recur_colors;
+
+ }
}
$hue += $hue_increment;
diff --git a/httemplate/graph/cust_bill_pkg_detail.cgi b/httemplate/graph/cust_bill_pkg_detail.cgi
new file mode 100644
index 0000000..642a9ec
--- /dev/null
+++ b/httemplate/graph/cust_bill_pkg_detail.cgi
@@ -0,0 +1,137 @@
+<% include('elements/monthly.html',
+ 'title' => $title. 'Rated Call Sales Report (Gross)',
+ 'graph_type' => 'Mountain',
+ 'items' => \@items,
+ 'params' => \@params,
+ 'labels' => \@labels,
+ 'graph_labels' => \@labels,
+ 'colors' => \@colors,
+ 'remove_empty' => 1,
+ 'bottom_total' => 1,
+ 'agentnum' => $agentnum,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#XXX or virtual
+my( $agentnum, $sel_agent ) = ('', '');
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+ $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ die "agentnum $agentnum not found!" unless $sel_agent;
+}
+my $title = $sel_agent ? $sel_agent->agent.' ' : '';
+
+#false lazinessish w/FS::cust_pkg::search_sql (previously search/cust_pkg.cgi)
+my $classnum = '';
+if ( $cgi->param('classnum') =~ /^(\d*)$/ ) {
+ $classnum = $1;
+
+ if ( $classnum ) { #a specific class
+
+ my $pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) );
+ die "classnum $classnum not found!" unless $pkg_class;
+ $title .= $pkg_class->classname.' ';
+
+ } elsif ( $classnum eq '' ) { #the empty class
+
+ $title .= 'Empty class ';
+ # FS::Report::Table::Monthly.pm has the converse view
+ $classnum = 0;
+
+ } elsif ( $classnum eq '0' ) { #all classes
+
+ # FS::Report::Table::Monthly.pm has the converse view
+ $classnum = '';
+ }
+}
+#eslaf
+
+my $use_override = 0;
+$use_override = 1 if ( $cgi->param('use_override') );
+
+my $usageclass = 0;
+my @usage_class = ();
+if ( $cgi->param('usageclass') =~ /^(\d*)$/ ) {
+ $usageclass = $1;
+
+ if ( $usageclass ) { #a specific class
+
+ @usage_class = ( qsearchs('usage_class', { 'classnum' => $usageclass } ) );
+ die "usage class $usageclass not found!" unless $usage_class[0];
+ $title .= $usage_class[0]->classname.' ';
+
+ } elsif ( $usageclass eq '' ) { #the empty class -- legacy
+
+ $title .= 'Empty usage class ';
+ @usage_class = ( '(empty usage class)' );
+
+ } elsif ( $usageclass eq '0' ) { #all classes
+
+ @usage_class = qsearch('usage_class', {} ); # { 'disabled' => '' } );
+ push @usage_class, '(empty usage class)';
+
+ }
+}
+#eslaf
+
+my $hue = 0;
+#my $hue_increment = 170;
+#my $hue_increment = 145;
+my $hue_increment = 125;
+
+my @items = ();
+my @params = ();
+my @labels = ();
+my @colors = ();
+
+foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
+
+ my $col_scheme = Color::Scheme->new
+ ->from_hue($hue) #->from_hex($agent->color)
+ ->scheme('analogic')
+ ;
+ my @recur_colors = ();
+ my @onetime_colors = ();
+
+ ### fixup the color handling for usage classes...
+ my $n = 0;
+
+ foreach my $usage_class ( @usage_class ) {
+
+ push @items, 'cust_bill_pkg_detail';
+
+ push @labels,
+ ( $sel_agent ? '' : $agent->agent.' ' ).
+ ( $usageclass eq '0'
+ ? ( ref($usage_class) ? $usage_class->classname : $usage_class )
+ : ''
+ );
+
+ my $row_classnum = ref($usage_class) ? $usage_class->classnum : 0;
+ my $row_agentnum = $agent->agentnum;
+ push @params, [ 'usageclass' => $row_classnum,
+ 'agentnum' => $row_agentnum,
+ 'use_override' => $use_override,
+ 'classnum' => $classnum,
+ ];
+
+ @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9]
+ unless @recur_colors;
+ @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11]
+ unless @onetime_colors;
+ push @colors, shift @recur_colors;
+
+ }
+
+ $hue += $hue_increment;
+
+}
+
+#use Data::Dumper;
+#warn Dumper(\@items);
+
+</%init>
diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html
index 5193bf4..51655a9 100644
--- a/httemplate/graph/report_cust_bill_pkg.html
+++ b/httemplate/graph/report_cust_bill_pkg.html
@@ -25,6 +25,17 @@
</TR>
-->
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD>
+ <TD>Separate sub-packages from parents</TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_usage" VALUE="1"></TD>
+ <TD>Separate rated usage from recurring fees</TD>
+</TR>
+
+
</TABLE>
<BR><INPUT TYPE="submit" VALUE="Display">
diff --git a/httemplate/graph/report_cust_bill_pkg_detail.html b/httemplate/graph/report_cust_bill_pkg_detail.html
new file mode 100644
index 0000000..3b85d52
--- /dev/null
+++ b/httemplate/graph/report_cust_bill_pkg_detail.html
@@ -0,0 +1,48 @@
+<% include('/elements/header.html', 'Usage Sales Report' ) %>
+
+<FORM ACTION="cust_bill_pkg_detail.cgi" METHOD="GET">
+
+<TABLE>
+
+<% include('/elements/tr-select-from_to.html' ) %>
+
+<% include('/elements/tr-select-agent.html',
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ )
+%>
+
+<% include('/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+%>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD>
+ <TD>Separate sub-packages from parents</TD>
+</TR>
+
+<% include('/elements/tr-select-table.html',
+ 'label' => 'Usage class: ',
+ 'element_name' => 'usageclass',
+ 'table' => 'usage_class',
+ 'name_col' => 'classname',
+ 'hashref' => { 'disabled' => '' },
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+%>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Display">
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/images/gray-black-side.png b/httemplate/images/gray-black-side.png
new file mode 100644
index 0000000..b384930
--- /dev/null
+++ b/httemplate/images/gray-black-side.png
Binary files differ
diff --git a/httemplate/misc/bulk_change_pkg.cgi b/httemplate/misc/bulk_change_pkg.cgi
index 9334985..7f47a84 100755
--- a/httemplate/misc/bulk_change_pkg.cgi
+++ b/httemplate/misc/bulk_change_pkg.cgi
@@ -7,17 +7,23 @@
<FORM ACTION="<% $p %>misc/process/bulk_change_pkg.cgi" METHOD=POST>
-<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords %>">
-% for my $param (qw(agentnum magic status classnum pkgpart)) {
-<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) %>">
+%# some false laziness w/search/cust_pkg.cgi
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords |h %>">
+% for my $param (qw(agentnum magic status classnum custom censustract)) {
+<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) |h %>">
% }
%
+% foreach my $pkgpart ($cgi->param('pkgpart')) {
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart |h %>">
+% }
+%
% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
%
- <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") %>">
- <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") %>">
- <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") %>">
- <INPUT TYPE="hidden" NAME="<% $field %>ending" VALUE="<% $cgi->param("${field}.ending") %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") |h %>">
+ <INPUT TYPE="hidden" NAME="<% $field %>ending" VALUE="<% $cgi->param("${field}.ending") |h %>">
% }
<% ntable('#cccccc') %>
@@ -28,10 +34,7 @@
'table' => 'part_pkg',
'name_col' => 'pkg',
'empty_label' => 'Select package',
- 'label_callback' => sub { $_[0]->pkgpart. ': '.
- $_[0]->pkg. ' - '.
- $_[0]->comment;
- },
+ 'label_callback' => sub { $_[0]->pkg_comment },
'element_name' => 'new_pkgpart',
'curr_value' => ( $cgi->param('error')
? scalar($cgi->param('new_pkgpart'))
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
index e0e5fd1..607ce13 100755
--- a/httemplate/misc/cancel_pkg.html
+++ b/httemplate/misc/cancel_pkg.html
@@ -17,7 +17,7 @@
<BR><BR>
-<% ucfirst($method) . " $pkgnum: " .$part_pkg->pkg. ' - ' .$part_pkg->comment %>
+<% ucfirst($method) %> <% $part_pkg->pkg_comment %>
<% ntable("#cccccc", 2) %>
% if ($method eq 'expire' || $method eq 'adjourn') {
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
index c4dfca2..16b7071 100755
--- a/httemplate/misc/change_pkg.cgi
+++ b/httemplate/misc/change_pkg.cgi
@@ -13,19 +13,15 @@
<% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %>
</TD>
</TR>
-
- <TR>
- <TH ALIGN="right">New package</TH>
- <TD COLSPAN=7>
- <% include('/elements/select-cust-part_pkg.html',
- 'cust_main' => $cust_main,
- 'element_name' => 'pkgpart',
- #'extra_sql' => ' AND pkgpart != '. $cust_pkg->pkgpart,
- 'curr_value' => scalar($cgi->param('pkgpart')),
- )
- %>
- </TD>
- </TR>
+
+ <% include('/elements/tr-select-cust-part_pkg.html',
+ 'pre_label' => 'New',
+ 'curr_value' => scalar($cgi->param('pkgpart')),
+ 'classnum' => $part_pkg->classnum,
+ 'cust_main' => $cust_main,
+ #'extra_sql' => ' AND pkgpart != '. $cust_pkg->pkgpart,
+ )
+ %>
<% include('/elements/tr-select-cust_location.html',
'cgi' => $cgi,
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
new file mode 100644
index 0000000..a249f03
--- /dev/null
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -0,0 +1,29 @@
+<% objToJson( \@return ) %>
+<%init>
+
+my( $custnum, $classnum ) = $cgi->param('arg');
+
+#XXX i guess i should be agent-virtualized. cause "packages a customer can
+#order" is such a huge deal
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+my %hash = ( 'disabled' => '' );
+if ( $classnum > 0 ) {
+ $hash{'classnum'} = $classnum;
+} elsif ( $classnum eq '' || $classnum == 0 ) {
+ $hash{'classnum'} = '';
+} #else -1, all classes, so don't set classnum
+
+my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'hashref' => \%hash,
+ 'extra_sql' =>
+ ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ),
+});
+
+my @return = map { $_->pkgpart => $_->pkg_comment }
+ sort { $a->pkg_comment cmp $b->pkg_comment }
+ @part_pkg;
+
+</%init>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
index b822c5d..9c1f984 100644
--- a/httemplate/misc/cust_main-import.cgi
+++ b/httemplate/misc/cust_main-import.cgi
@@ -56,7 +56,7 @@ Import a file containing customer records.
<SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) {
- <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+ <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg_comment %></OPTION>
% }
</SELECT>
diff --git a/httemplate/misc/delay_susp_pkg.html b/httemplate/misc/delay_susp_pkg.html
index 1158a35..d4a6da1 100755
--- a/httemplate/misc/delay_susp_pkg.html
+++ b/httemplate/misc/delay_susp_pkg.html
@@ -12,7 +12,7 @@
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
<BR><BR>
-<% "Delay automatic suspension of $pkgnum: " .$part_pkg->pkg. ' - ' .$part_pkg->comment %>
+<% "Delay automatic suspension of " .$part_pkg->pkg_comment %>
<% ntable("#cccccc", 2) %>
<TR>
diff --git a/httemplate/misc/delete-cust_bill.html b/httemplate/misc/delete-cust_bill.html
new file mode 100644
index 0000000..3a642b0
--- /dev/null
+++ b/httemplate/misc/delete-cust_bill.html
@@ -0,0 +1,21 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Delete invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+my $custnum = $cust_bill->custnum;
+
+my $error = $cust_bill->delete;
+
+</%init>
diff --git a/httemplate/misc/delete-phone_device.html b/httemplate/misc/delete-phone_device.html
new file mode 100755
index 0000000..7220c41
--- /dev/null
+++ b/httemplate/misc/delete-phone_device.html
@@ -0,0 +1,23 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect($p. "view/svc_phone.cgi?". $svcnum) %>
+% }
+<%init>
+
+# :/ needs agent-virt so you can't futz with arbitrary devices
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+#untaint devicenum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal devicenum";
+my $devicenum = $1;
+
+my $phone_device = qsearchs('phone_device', { 'devicenum' => $devicenum } );
+my $svcnum = $phone_device->svcnum;
+
+my $error = $phone_device->delete;
+
+</%init>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
index 57905da..01bf5d2 100644
--- a/httemplate/misc/download-batch.cgi
+++ b/httemplate/misc/download-batch.cgi
@@ -1,146 +1,9 @@
-%if ($format eq "BoM") {
-%
-% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) =
-% $conf->config("batchconfig-$format");
-%
-<% sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,"").
- sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct )
- %>
-%
-%}elsif ($format eq "PAP"){
-%
-% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) =
-% $conf->config("batchconfig-$format");
-%
-<% sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"") %>
-%
-%
-%}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
-%# 1;
-%}elsif ($format eq "csv-chase_canada-E-xactBatch"){
-%
-% my($origid) = $conf->config("batchconfig-$format");
-<% sprintf( '$$E-xactBatchFileV1.0$$%s:%03u$$%s',$sdate,$pay_batch->batchnum, $origid)
- %>
-%
-%}elsif ($format eq "ach-spiritone"){
-%# 1;
-%}else{
-% die "Unknown format for batch in batchconfig. \n";
-%}
-%
-%
-%for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
-% qsearch('cust_pay_batch',
-% {'batchnum'=>$pay_batch->batchnum} )
-%) {
-%
-% $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
-% my( $mon, $y ) = ( $2, $1 );
-% if ( $conf->exists('batch-increment_expiration') ) {
-% my( $curmon, $curyear ) = (localtime(time))[4,5];
-% $curmon++; $curyear-=100;
-% $y++ while $y < $curyear || ( $y == $curyear && $mon < $curmon );
-% }
-% $mon = "0$mon" if $mon =~ /^\d$/;
-% $y = "0$y" if $y =~ /^\d$/;
-% my $exp = "$mon$y";
-%
-% if ( $first_download ) {
-% my $balance = $cust_pay_batch->cust_main->balance;
-% if ( $balance <= 0 ) {
-% my $error = $cust_pay_batch->delete;
-% if ( $error ) {
-% $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-% die $error;
-% }
-% next;
-% } elsif ( $balance < $cust_pay_batch->amount ) {
-% $cust_pay_batch->amount($balance);
-% my $error = $cust_pay_batch->replace;
-% if ( $error ) {
-% $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-% die $error;
-% }
-% #} elsif ( $balance > $cust_pay_batch->amount ) {
-% }
-% }
-%
-% $batchcount++;
-% $batchtotal += $cust_pay_batch->amount;
-%
-% if ($format eq "BoM") {
-%
-% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
-%
-<% sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->paybatchnum) %>
-%
-%
-% } elsif ($format eq "PAP"){
-%
-% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
-%
-<% sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %>
-%
-%
-% } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") {
-%
-%
-,,,,<% $cust_pay_batch->payinfo %>,<% $exp %>,<% $cust_pay_batch->amount %>,<% $cust_pay_batch->paybatchnum %>
-%
-%
-% } elsif ($format eq "csv-chase_canada-E-xactBatch"){
-%
-% my $payname=$cust_pay_batch->payname; $payname =~ tr/",/ /; #payinfo too? :P
-<% $cust_pay_batch->paybatchnum %>,<% $cust_pay_batch->custnum %>,<% $cust_pay_batch->invnum %>,"<% $payname %>",00,<% $cust_pay_batch->payinfo %>,<% $cust_pay_batch->amount %>,<% $exp %>,,
-%
-%
-% }elsif ($format eq "ach-spiritone"){
-%
-% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
-% my $payname=$cust_pay_batch->first. " ". $cust_pay_batch->last;
-% $payname =~ tr/",/ /; #payinfo too?
-% my $batchline = qq!"$payname","!.$cust_pay_batch->paybatchnum.
-% qq!","$aba","$account","27","!.$cust_pay_batch->amount.
-% qq!","27","0.00"!;
-% push @batchlines, $batchline;
-<% $batchline %>
-%
-% } else {
-% die "I'm already dead, but you did not know that.\n";
-% }
-%
-%}
-%
-%if ($format eq "BoM") {
-%
-%
-<% sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ).
- sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %>
-%
-%
-%} elsif ($format eq "PAP"){
-%
-%
-<% sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %>
-%
-%
-%} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
-% #1;
-%} elsif ($format eq "csv-chase_canada-E-xactBatch"){
-% #1;
-%} elsif ($format eq "ach-spiritone"){
-% #1;
-%} else {
-% die "I'm already dead (again), but you did not know that.\n";
-%}
-%
-<%init>
+<% $pay_batch->export_batch($format) %>
-my $conf=new FS::Conf;
+<%init>
#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
-http_header('Content-Type' => 'text/plain' );
+http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
my $batchnum;
if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
@@ -152,62 +15,9 @@ if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
my $format;
if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
$format = $1;
-} else {
- $format = $conf->config('batch-default_format');
-}
-
-my $autopost;
-if ( $format eq 'ach-spiritone' ) {
- $autopost = 1;
-}else{
- $autopost = 0;
-}
-
-my $oldAutoCommit = $FS::UID::AutoCommit;
-local $FS::UID::AutoCommit = 0;
-my $dbh = dbh;
-
-my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} );
-my $first_download = 1;
-unless ($pay_batch) {
- $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'I'} )
- if $FS::CurrentUser::CurrentUser->access_right('Reprocess batches');
- $first_download = 0;
}
-die "No pending batch. \n" unless $pay_batch;
-my $error = $pay_batch->set_status('I');
-die "error updating batch status: $error\n" if $error;
+my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
+die "Batch not found: '$batchnum'" if !$pay_batch;
-my $batchtotal=0;
-my $batchcount=0;
-
-my (@date)=localtime($pay_batch->download);
-my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1);
-my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1).
- sprintf("%02d", $date[5] % 100);
-my $sdate = sprintf("%02d", $date[5] % 100).'/'.sprintf("%02d", $date[4] + 1).
- '/'.sprintf("%02d", $date[3]);
-
-my @batchlines = ();
</%init>
-<%cleanup>
-if ($autopost) {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- my $fh = new File::Temp(
- TEMPLATE => 'paybatch.'. $batchnum .'.XXXXXXXX',
- DIR => $dir,
- ) or die "can't open temp file: $!\n";
-
- print $fh map{ "$_\n" } @batchlines;
- seek $fh, 0, 0;
-
- $error = $pay_batch->import_results( 'filehandle' => $fh,
- 'format' => $format,
- );
- die $error if $error;
-}
-
-$dbh->commit or die $dbh->errstr if $oldAutoCommit;
-
-</%cleanup>
diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html
index 0d3d622..f644db9 100644
--- a/httemplate/misc/email-customers.html
+++ b/httemplate/misc/email-customers.html
@@ -126,7 +126,7 @@ die "access denied"
my %search = $cgi->Vars;
delete $search{$_} for qw( magic from subject html_body text_body );
$search{$_} = [ split(/\0/, $search{$_}) ]
- foreach grep $search{$_} =~ /\0/, keys %search;
+ foreach grep { $_ eq 'payby' || $search{$_} =~ /\0/ } keys %search;
my $title = 'Bulk send customer notices';
diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi
index 748eaa1..f37f769 100755
--- a/httemplate/misc/link.cgi
+++ b/httemplate/misc/link.cgi
@@ -58,6 +58,7 @@ die "access denied"
my %link_field = (
'svc_acct' => 'username',
'svc_domain' => 'domain',
+ 'svc_phone' => 'phonenum',
);
my %link_field2 = (
diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi
index 5b3470c..8c158bd 100644
--- a/httemplate/misc/meta-import.cgi
+++ b/httemplate/misc/meta-import.cgi
@@ -46,7 +46,7 @@ Import data from a DBI data source<BR><BR>
First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) {
- <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+ <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg_comment %></OPTION>
% }
</SELECT><BR><BR>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index 2c83351..a7571ca 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -1,5 +1,10 @@
<% include('/elements/header-popup.html', 'Order new package' ) %>
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
<SCRIPT TYPE="text/javascript">
function enable_order_pkg () {
@@ -19,18 +24,42 @@
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>">
<% ntable("#cccccc", 2) %>
+<% include('/elements/tr-select-cust-part_pkg.html',
+ 'curr_value' => $pkgpart,
+ 'classnum' => -1,
+ 'cust_main' => $cust_main,
+ 'onchange' => 'enable_order_pkg',
+ )
+%>
+
+%# false laziness w/edit/quick-charge.html
<TR>
- <TH ALIGN="right">Package</TH>
- <TD COLSPAN=7>
- <% include('/elements/select-cust-part_pkg.html',
- 'curr_value' => $pkgpart,
- 'cust_main' => $cust_main,
- 'onchange' => 'enable_order_pkg',
- )
- %>
+ <TH ALIGN="right">Start date </TD>
+ <TD COLSPAN=6>
+ <INPUT TYPE = "text"
+ NAME = "start_date"
+ SIZE = 32
+ ID = "start_date_text"
+ VALUE = "<% $start_date %>"
+ >
+ <IMG SRC = "../images/calendar.png"
+ ID = "start_date_button"
+ STYLE = "cursor: pointer"
+ TITLE = "Select date"
+ >
+ <FONT SIZE=-1>(leave blank to start immediately)</FONT>
</TD>
</TR>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "start_date_text",
+ ifFormat: "%m/%d/%Y",
+ button: "start_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
% if ( $conf->exists('pkg_referral') ) {
<% include('/elements/tr-select-part_referral.html',
'curr_value' => scalar( $cgi->param('refnum') ), #get rid of empty_label first# || $cust_main->refnum,
@@ -72,4 +101,8 @@ my $cust_main = qsearchs({
my $pkgpart = scalar($cgi->param('pkgpart'));
+my $format = "%m/%d/%Y %T %z (%Z)"; #false laziness w/REAL_cust_pkg.cgi?
+my $start_date = $cust_main->next_bill_date;
+$start_date = $start_date ? time2str($format, $start_date) : '';
+
</%init>
diff --git a/httemplate/misc/part_device-import.html b/httemplate/misc/part_device-import.html
new file mode 100644
index 0000000..7bd6404
--- /dev/null
+++ b/httemplate/misc/part_device-import.html
@@ -0,0 +1,53 @@
+<% include("/elements/header.html", 'Import device types') %>
+
+Import a file containing phone device types, one per line.
+<BR><BR>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'PartDeviceImportForm',
+ 'action' => 'process/part_device-import.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format', ],
+ 'message' => 'Device type import successful',
+ 'url' => $p.'browse/part_device.html',
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => 'Filename',
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Import file"
+ onClick = "document.PartDeviceImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<BR>
+
+Upload file can be a text file or Excel spreadsheet. If an Excel spreadsheet,
+ should have an .XLS extension.
+<BR><BR>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi
new file mode 100644
index 0000000..0602561
--- /dev/null
+++ b/httemplate/misc/part_svc-columns.cgi
@@ -0,0 +1,13 @@
+<% objToJson(\@output) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $pkgpart_svcpart = $cgi->param('arg');
+$pkgpart_svcpart =~ /^\d+_(\d+)$/;
+my $part_svc = qsearchs('part_svc', { 'svcpart' => $1 }) if $1;
+
+my @output = map { ( $_->columnname, $_->columnflag, $_->columnvalue ) }
+ $part_svc->all_part_svc_column;
+
+</%init>
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
index 0047004..813b560 100644
--- a/httemplate/misc/payment.cgi
+++ b/httemplate/misc/payment.cgi
@@ -12,23 +12,66 @@
<% ntable('#cccccc') %>
<TR>
- <TD ALIGN="right">Payment amount</TD>
- <TD>
+ <TH ALIGN="right">Payment amount</TH>
+ <TD COLSPAN=7>
<TABLE><TR><TD BGCOLOR="#ffffff">
- $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<% $balance > 0 ? sprintf("%.2f", $balance) : '' %>">
+ <% $money_char %><INPUT NAME = "amount"
+ TYPE = "text"
+ VALUE = "<% $amount %>"
+ SIZE = 8
+ STYLE = "text-align:right;"
+% if ( $fee ) {
+ onChange = "amount_changed(this)"
+ onKeyDown = "amount_changed(this)"
+ onKeyUp = "amount_changed(this)"
+ onKeyPress = "amount_changed(this)"
+% }
+ >
+ </TD><TD BGCOLOR="#cccccc">
+% if ( $fee ) {
+ <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>">
+ <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>">
+ <B><FONT SIZE='+1'><% $fee_op %></FONT>
+ <% $money_char . $fee %>
+ </B>
+ <% $fee_pkg->pkg |h %>
+ <B><FONT SIZE='+1'>=</FONT></B>
+ </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue">
+ <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>
+
+% }
</TD></TR></TABLE>
</TD>
</TR>
+% if ( $fee ) {
+
+ <SCRIPT TYPE="text/javascript">
+
+ function amount_changed(what) {
+
+
+ var total = '';
+ if ( what.value.length ) {
+ total = parseFloat(what.value) <% $fee_op %> <% $fee %>;
+ /* total = Math.round(total*100)/100; */
+ total = '<% $money_char %>' + total.toFixed(2);
+ }
+
+ var total_cell = document.getElementById('ajax_total_cell');
+ total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>';
+
+ }
+
+ </SCRIPT>
+
+% }
+
+
% if ( $payby eq 'CARD' ) {
%
% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
% my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
-% my $address1 = $cust_main->address1;
-% my $address2 = $cust_main->address2;
-% my $city = $cust_main->city;
-% my $state = $cust_main->state;
-% my $zip = $cust_main->zip;
% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
% $payinfo = $cust_main->paymask;
% $paycvv = $cust_main->paycvv;
@@ -37,13 +80,13 @@
% }
<TR>
- <TD ALIGN="right">Card&nbsp;number</TD>
- <TD>
+ <TH ALIGN="right">Card&nbsp;number</TH>
+ <TD COLSPAN=7>
<TABLE>
<TR>
<TD>
<INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%$payinfo%>"> </TD>
- <TD>Exp.</TD>
+ <TH>Exp.</TH>
<TD>
<SELECT NAME="month">
% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) {
@@ -68,51 +111,23 @@
</TD>
</TR>
<TR>
- <TD ALIGN="right">CVV2</TD>
+ <TH ALIGN="right">CVV2</TH>
<TD><INPUT TYPE="text" NAME="paycvv" VALUE="<% $paycvv %>" SIZE=4 MAXLENGTH=4>
(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)
</TD>
</TR>
<TR>
- <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+ <TH ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TH>
<TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%$payname%>"></TD>
- </TR><TR>
- <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
- <TD>
- <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%$address1%>">
- </TD>
- </TR><TR>
- <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
- <TD>
- <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%$address2%>">
- </TD>
- </TR><TR>
- <TD ALIGN="right">City</TD>
- <TD>
- <TABLE>
- <TR>
- <TD>
- <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%$city%>">
- </TD>
- <TD>State</TD>
- <TD>
- <SELECT NAME="state">
-% for ( @states ) {
-
- <OPTION<% $_ eq $state ? ' SELECTED' : '' %>><% $_ %>
-% }
-
- </SELECT>
- </TD>
- <TD>Zip</TD>
- <TD>
- <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%$zip%>">
- </TD>
- </TR>
- </TABLE>
- </TD>
</TR>
+ <% include( '/elements/location.html',
+ 'object' => $cust_main, #XXX errors???
+ 'no_asterisks' => 1,
+ 'address1_label' => 'Card billing address',
+ )
+ %>
+
% } elsif ( $payby eq 'CHEK' ) {
%
% my( $payinfo1, $payinfo2, $payname, $ss, $paytype, $paystate,
@@ -270,16 +285,51 @@ my $balance = $cust_main->balance;
my $payinfo = '';
-#false laziness w/selfservice make_payment.html shortcut for one-country
my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+#false laziness w/selfservice make_payment.html shortcut for one-country
my %states = map { $_->state => 1 }
qsearch('cust_main_county', {
'country' => $conf->config('countrydefault') || 'US'
} );
my @states = sort { $a cmp $b } keys %states;
+my $fee = '';
+my $fee_pkg = '';
+my $fee_display = '';
+my $fee_op = '';
+my $num_payments = scalar($cust_main->cust_pay);
+#handle old cust_main.pm (remove...)
+$num_payments = scalar( @{ [ $cust_main->cust_pay ] } )
+ unless defined $num_payments;
+if ( $conf->config('manual_process-pkgpart')
+ and ! $conf->exists('manual_process-skip_first') || $num_payments
+ )
+{
+
+ $fee_display = $conf->config('manual_process-display') || 'add';
+ $fee_op = $fee_display eq 'add' ? '+' : '-';
+
+ $fee_pkg =
+ qsearchs('part_pkg', { pkgpart=>$conf->config('manual_process-pkgpart') } );
+
+ #well ->unit_setup or ->calc_setup both call for a $cust_pkg
+ # (though ->unit_setup doesn't use it...)
+ $fee = $fee_pkg->option('setup_fee')
+ if $fee_pkg; #in case.. better than dying with a perl traceback
+
+}
+
+my $amount = '';
+if ( $balance > 0 ) {
+ $amount = $balance;
+ $amount += $fee
+ if $fee && $fee_display eq 'subtract';
+ $amount = sprintf("%.2f", $amount);
+}
+
my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32;
</%init>
-
-
diff --git a/httemplate/misc/ping.html b/httemplate/misc/ping.html
new file mode 100644
index 0000000..4f0360e
--- /dev/null
+++ b/httemplate/misc/ping.html
@@ -0,0 +1,102 @@
+<% include('/elements/header-popup.html', "Ping $ip" ) %>
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p. 'misc/xmlhttp-ping.html',
+ 'subs' => [ 'ping' ],
+ )
+%>
+
+%# <img src="<%$p%>images/bullet_red.png" border=0>
+
+
+<%ntable("#cccccc", 2)%>
+
+<TR>
+ <TD>Status</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_status">Checking...</TD>
+</TR>
+<TR>
+ <TD>Packet loss</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_packetloss"></TD>
+</TR>
+<TR>
+ <TD>Latency</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_latency"></TD>
+</TR>
+<TR>
+ <TD>Packets</TD>
+ <TD BGCOLOR="#ffffff" ID="ping_packets"></TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<CENTER>
+<INPUT TYPE="button" VALUE="Close" onClick="parent.nd(1);">
+</CENTER>
+
+<SCRIPT TYPE="text/javascript">
+
+ var fails = 0;
+ var pongs = 0;
+ var totaltime = 0;
+ var avg = 0;
+
+ function ping_update ( updatetext ) {
+ var pingArray = eval('(' + updatetext + ')');
+ var status = pingArray[0];
+ var rtt = pingArray[1];
+
+ if ( status == 0 ) {
+ fails++;
+ } else if ( status == 1 ) {
+ pongs++;
+ totaltime = totaltime + rtt;
+ avg = totaltime / pongs;
+ }
+
+ var loss = 100 * fails / ( fails + pongs );
+
+ var statusCell = document.getElementById('ping_status');
+ var packetlossCell = document.getElementById('ping_packetloss');
+ var latencyCell = document.getElementById('ping_latency');
+ var packetsCell = document.getElementById('ping_packets');
+
+ var status = '';
+ // red conditions
+ if ( loss == 100 ) {
+ status = '<FONT COLOR="#ff0000">Unreachable</FONT>';
+ } else
+ // yellow conditions
+ if ( loss > 50 ) {
+ status = '<FONT COLOR="#ff9900">High packet loss</FONT>';
+ } else
+ if ( avg > 1 ) {
+ status = '<FONT COLOR="#ff9900">High latency</FONT>';
+ } else {
+ status = '<FONT COLOR="#00cc00">Up</FONT>';
+ }
+
+ statusCell.innerHTML = '<B>' + status + '</B>';
+ packetlossCell.innerHTML = '<B>' + Math.round(loss) + '%</B>';
+ if ( avg > 0 ) {
+ latencyCell.innerHTML = '<B>' + Math.round( avg*1000 ) + 'ms</B>';
+ }
+ var packets = fails + pongs;
+ packetsCell.innerHTML = '<B>' + packets + '</B>';
+
+ setTimeout( "ping('<%$ip%>', ping_update)", 1000 );
+
+ }
+
+ ping( '<%$ip%>', ping_update );
+
+</SCRIPT>
+
+<%init>
+
+my($query) = $cgi->keywords;
+$query =~ /^([\d\.]+)$/ or die 'Illegal IP';
+my $ip = $1;
+
+</%init>
diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi
index df15dca..77546f3 100755
--- a/httemplate/misc/process/link.cgi
+++ b/httemplate/misc/process/link.cgi
@@ -1,14 +1,20 @@
%unless ($error) {
% #no errors, so let's view this customer.
% my $custnum = $new->cust_pkg->custnum;
-<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum#cust_pkg$pkgnum" ) %>
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag" ) %>
%} else {
% errorpage($error);
%}
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('View/link unlinked services');
+ unless $curuser->access_right('View/link unlinked services');
my $DEBUG = 0;
diff --git a/httemplate/misc/process/part_device-import.html b/httemplate/misc/process/part_device-import.html
new file mode 100644
index 0000000..eac111a
--- /dev/null
+++ b/httemplate/misc/process/part_device-import.html
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_device::process_batch_import', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi
index 2baca1e..1e9501d 100644
--- a/httemplate/misc/process/payment.cgi
+++ b/httemplate/misc/process/payment.cgi
@@ -32,6 +32,11 @@ $cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/
my $amount = $1;
errorpage("amount <= 0") unless $amount > 0;
+if ( $cgi->param('fee') =~ /^\s*(\d*(\.\d\d)?)\s*$/ ) {
+ my $fee = $1;
+ $amount = sprintf('%.2f', $amount + $fee);
+}
+
$cgi->param('year') =~ /^(\d+)$/
or errorpage("illegal year ". $cgi->param('year'));
my $year = $1;
@@ -44,7 +49,7 @@ $cgi->param('payby') =~ /^(CARD|CHEK)$/
or errorpage("illegal payby ". $cgi->param('payby'));
my $payby = $1;
my %payby2fields = (
- 'CARD' => [ qw( address1 address2 city state zip ) ],
+ 'CARD' => [ qw( address1 address2 city county state zip country ) ],
'CHEK' => [ qw( ss paytype paystate stateid stateid_state ) ],
);
my %type = ( 'CARD' => 'credit card',
@@ -143,6 +148,22 @@ if ( $cgi->param('batch') ) {
);
errorpage($error) if $error;
+ #no error, so order the fee package if applicable...
+ if ( $cgi->param('fee_pkgpart') =~ /^(\d+)$/ ) {
+
+ my $cust_pkg = new FS::cust_pkg { 'pkgpart' => $1 };
+
+ my $error = $cust_main->order_pkg( 'cust_pkg' => $cust_pkg );
+ errorpage("payment processed successfully, but error ordering fee: $error")
+ if $error;
+
+ #and generate an invoice for it now too
+ $error = $cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
+ errorpage("payment processed and fee ordered sucessfully, but error billing fee: $error")
+ if $error;
+
+ }
+
$cust_main->apply_payments;
}
diff --git a/httemplate/misc/process/rate_edit_excel.html b/httemplate/misc/process/rate_edit_excel.html
new file mode 100644
index 0000000..acd5f49
--- /dev/null
+++ b/httemplate/misc/process/rate_edit_excel.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::rate_detail::process_edit_import', $cgi;
+
+</%init>
+
diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html
index 147b953..5f68bf1 100755
--- a/httemplate/misc/process/recharge_svc.html
+++ b/httemplate/misc/process/recharge_svc.html
@@ -1,62 +1,13 @@
-%unless ($error) {
-%
-% my ($amount, $seconds, $up, $down, $total) = (0, 0, 0, 0, 0);
-% #should probably use payby.pm but whatever
-% if ($payby eq 'PREP') {
-% $error = $cust_main->get_prepay($prepaid, \$amount, \$seconds, \$up, \$down, \$total)
-% || $svc_acct->increment_seconds($seconds)
-% || $svc_acct->increment_upbytes($up)
-% || $svc_acct->increment_downbytes($down)
-% || $svc_acct->increment_totalbytes($total)
-% || $cust_main->insert_cust_pay_prepay( $amount, $prepaid );
-% } elsif ( $payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP)$/ ) {
-% my $part_pkg = $svc_acct->cust_svc->cust_pkg->part_pkg;
-% $amount = $part_pkg->option('recharge_amount', 1);
-% my %rhash = map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_) }
-% grep { $part_pkg->option($_, 1) }
-% qw ( recharge_seconds recharge_upbytes recharge_downbytes
-% recharge_totalbytes );
-%
-% my $description = "Recharge";
-% $description .= " $rhash{seconds}s" if $rhash{seconds};
-% $description .= " $rhash{upbytes} up" if $rhash{upbytes};
-% $description .= " $rhash{downbytes} down" if $rhash{downbytes};
-% $description .= " $rhash{totalbytes} total" if $rhash{totalbytes};
-%
-% $error = $cust_main->charge($amount, "Recharge " . $svc_acct->label,
-% $description, $part_pkg->taxclass);
-%
-% if ($part_pkg->option('recharge_reset', 1)) {
-% $error ||= $svc_acct->set_usage(\%rhash);
-% }else{
-% $error ||= $svc_acct->recharge(\%rhash);
-% }
-%
-% my $old_balance = $cust_main->balance;
-% $error ||= $cust_main->bill;
-% $error ||= $cust_main->apply_payments_and_credits;
-% my $bill_error = $cust_main->collect('realtime' => 1) unless $error;
-% $error ||= "Failed to collect - $bill_error"
-% if $cust_main->balance > $old_balance && $cust_main->balance > 0
-% && $payby ne 'BILL';
-%
-% } else {
-% $error = "fatal error - unknown payby: $payby";
-% }
-%}
-%
%if ($error) {
% $cgi->param('error', $error);
-% $dbh->rollback if $oldAutoCommit;
-% print $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string );
-%}
-%$dbh->commit or die $dbh->errstr if $oldAutoCommit;
-%
+<% $cgi->redirect(popurl(2). "recharge_svc.html?". $cgi->query_string ) %>
+%} else {
<% header("Package recharged") %>
<SCRIPT TYPE="text/javascript">
window.top.location.reload();
</SCRIPT>
</BODY></HTML>
+%}
<%init>
my $conf = new FS::Conf;
@@ -89,4 +40,52 @@ my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+unless ($error) {
+
+ #should probably use payby.pm but whatever
+ if ($payby eq 'PREP') {
+ $error = $cust_main->recharge_prepay( $prepaid );
+ } elsif ( $payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP)$/ ) {
+ my $part_pkg = $svc_acct->cust_svc->cust_pkg->part_pkg;
+ my $amount = $part_pkg->option('recharge_amount', 1);
+ my %rhash = map { $_ =~ /^recharge_(.*)$/; $1, $part_pkg->option($_) }
+ grep { $part_pkg->option($_, 1) }
+ qw ( recharge_seconds recharge_upbytes recharge_downbytes
+ recharge_totalbytes );
+
+ my $description = "Recharge";
+ $description .= " $rhash{seconds}s" if $rhash{seconds};
+ $description .= " $rhash{upbytes} up" if $rhash{upbytes};
+ $description .= " $rhash{downbytes} down" if $rhash{downbytes};
+ $description .= " $rhash{totalbytes} total" if $rhash{totalbytes};
+
+ $error = $cust_main->charge($amount, "Recharge " . $svc_acct->label,
+ $description, $part_pkg->taxclass);
+
+ if ($part_pkg->option('recharge_reset', 1)) {
+ $error ||= $svc_acct->set_usage(\%rhash, 'null' => 1);
+ }else{
+ $error ||= $svc_acct->recharge(\%rhash);
+ }
+
+ my $old_balance = $cust_main->balance;
+ $error ||= $cust_main->bill;
+ $error ||= $cust_main->apply_payments_and_credits;
+ my $bill_error = $cust_main->collect('realtime' => 1) unless $error;
+ $error ||= "Failed to collect - $bill_error"
+ if $cust_main->balance > $old_balance && $cust_main->balance > 0
+ && $payby ne 'BILL';
+
+ } else {
+ $error = "fatal error - unknown payby: $payby";
+ }
+
+}
+
+if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+} else {
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
</%init>
diff --git a/httemplate/misc/process/tax-fetch_and_import.cgi b/httemplate/misc/process/tax-fetch_and_import.cgi
new file mode 100644
index 0000000..553c755
--- /dev/null
+++ b/httemplate/misc/process/tax-fetch_and_import.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_download_and_update', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/tax-fetch_and_replace.cgi b/httemplate/misc/process/tax-fetch_and_replace.cgi
new file mode 100644
index 0000000..1a9b626
--- /dev/null
+++ b/httemplate/misc/process/tax-fetch_and_replace.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_download_and_reload', $cgi;
+
+</%init>
diff --git a/httemplate/misc/process/tax-import.cgi b/httemplate/misc/process/tax-import.cgi
index 016d4b6..f800dbd 100644
--- a/httemplate/misc/process/tax-import.cgi
+++ b/httemplate/misc/process/tax-import.cgi
@@ -2,7 +2,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
my $server = new FS::UI::Web::JSRPC 'FS::tax_rate::process_batch_import', $cgi;
diff --git a/httemplate/misc/rate_edit_excel.html b/httemplate/misc/rate_edit_excel.html
new file mode 100644
index 0000000..e73133c
--- /dev/null
+++ b/httemplate/misc/rate_edit_excel.html
@@ -0,0 +1,61 @@
+<% include('/elements/header.html', 'Edit rates with Excel' ) %>
+
+<% include( '/elements/form-file_upload.html',
+ 'name' => 'RateImportForm',
+ 'action' => 'process/rate_edit_excel.html',
+ 'num_files' => 1,
+ 'fields' => [ 'format' ],
+ 'message' => 'Rate edit successful',
+ 'url' => $p."browse/rate_region.html",
+ )
+%>
+
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="left">1. Download current rates:</TH>
+ <TD>
+ <A HREF="<%$p%>/browse/rate_region.html?show_rates=1;_type=regions.xls">Download rate spreadsheet</A>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="left" COLSPAN=2>2. Edit rates with Excel (or other .XLS-compatible application)</TH>
+ </TR>
+
+ <TR>
+ <TD ALIGN="left" COLSPAN=2>
+ &nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;To add rates, add four columns like an existing rate, with headers starting with "NEW: Rate Name" or "Rate Name".<BR>
+ &nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;<FONT SIZE="-2"><I>For rate addition, protection can be turned off in Excel via the Tools-&gt;Protection-&gt;Unprotect Sheet menu command. Note that only new rates can be added; modified grayed out cells will not be imported.</I></FONT>
+ </TD>
+ </TR>
+
+ <% include( '/elements/file-upload.html',
+ 'field' => 'file',
+ 'label' => '3. Upload edited rate file: ',
+ 'label_align' => 'left',
+ )
+ %>
+
+ <INPUT TYPE="hidden" NAME="format" VALUE="default">
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "Upload"
+ onClick = "document.RateImportForm.submit.disabled=true;"
+ >
+ </TD>
+ </TR>
+
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/misc/send-invoice.cgi b/httemplate/misc/send-invoice.cgi
new file mode 100644
index 0000000..32dfe27
--- /dev/null
+++ b/httemplate/misc/send-invoice.cgi
@@ -0,0 +1,30 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%once>
+
+my %method = ( map { $_=>1 } qw( email print fax_invoice ) );
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $invnum = $cgi->param('invnum');
+my $template = $cgi->param('template');
+my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name');
+my $method = $cgi->param('method');
+
+$method .= '_invoice' if $method eq 'fax'; #!
+
+die "unknown method $method" unless $method{$method};
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+$cust_bill->$method({ 'template' => $template,
+ 'notice_name' => $notice_name,
+ });
+
+my $custnum = $cust_bill->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/send-statement.cgi b/httemplate/misc/send-statement.cgi
new file mode 100755
index 0000000..e363fbd
--- /dev/null
+++ b/httemplate/misc/send-statement.cgi
@@ -0,0 +1,28 @@
+<% $cgi->redirect("${p}view/cust_main.cgi?$custnum") %>
+<%once>
+
+my %method = map { $_=>1 } qw( email print fax_invoice );
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Resend invoices');
+
+my $statementnum = $cgi->param('statementnum');
+my $template = $cgi->param('template') || 'statement'; #XXX configure... via event?? eh..
+my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name');
+my $method = $cgi->param('method');
+
+$method .= '_invoice' if $method eq 'fax'; #!
+
+die "unknown method $method" unless $method{$method};
+
+my $cust_statement = qsearchs('cust_statement',{'statementnum'=>$statementnum});
+die "Can't find statement!\n" unless $cust_statement;
+
+$cust_statement->$method({ 'template' => $template });
+
+my $custnum = $cust_statement->getfield('custnum');
+
+</%init>
diff --git a/httemplate/misc/states.cgi b/httemplate/misc/states.cgi
index cf2b46e..02b7be4 100644
--- a/httemplate/misc/states.cgi
+++ b/httemplate/misc/states.cgi
@@ -1,7 +1,7 @@
-%
-%
-% my $country = $cgi->param('arg');
-% my @output = states_hash($country);
-%
-%
[ <% join(', ', map { qq("$_") } @output) %> ]
+<%init>
+
+my $country = $cgi->param('arg');
+my @output = states_hash($country);
+
+</%init>
diff --git a/httemplate/misc/tax-fetch_and_import.cgi b/httemplate/misc/tax-fetch_and_import.cgi
new file mode 100644
index 0000000..33a6c9b
--- /dev/null
+++ b/httemplate/misc/tax-fetch_and_import.cgi
@@ -0,0 +1,48 @@
+<% include("/elements/header.html",'Tax Rate Download and Import') %>
+
+Import a tax data update.
+<BR><BR>
+
+<% include( '/elements/progress-init.html', 'TaxRateImport',[ 'format', ],
+ 'process/tax-fetch_and_import.cgi', { 'message' => 'Tax rates imported' },
+ )
+%>
+
+<FORM NAME="TaxRateImport" ACTION="javascript:void()" METHOD="POST">
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <OPTION VALUE="cch">CCH import
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TH ALIGN="right">Update Password</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="password">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ VALUE = "Download and Import"
+ onClick = "document.TaxRateImport.submit.disabled=true; process();"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/tax-fetch_and_replace.cgi b/httemplate/misc/tax-fetch_and_replace.cgi
new file mode 100644
index 0000000..3290a3c
--- /dev/null
+++ b/httemplate/misc/tax-fetch_and_replace.cgi
@@ -0,0 +1,48 @@
+<% include("/elements/header.html",'Tax Rate Download and Import') %>
+
+Replace tax data.
+<BR><BR>
+
+<% include( '/elements/progress-init.html', 'TaxRateImport',[ 'format', ],
+ 'process/tax-fetch_and_replace.cgi', { 'message' => 'Tax rates replaced' },
+ )
+%>
+
+<FORM NAME="TaxRateImport" ACTION="javascript:void()" METHOD="POST">
+<% &ntable("#cccccc", 2) %>
+
+ <TR>
+ <TH ALIGN="right">Format</TH>
+ <TD>
+ <SELECT NAME="format">
+ <OPTION VALUE="cch">CCH import
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TH ALIGN="right">Update Password</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="password">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px">
+ <INPUT TYPE = "submit"
+ VALUE = "Download and Import"
+ onClick = "document.TaxRateImport.submit.disabled=true; process();"
+ >
+ </TD>
+ </TR>
+
+</TABLE>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/tax-import.cgi b/httemplate/misc/tax-import.cgi
index a695e97..5116e54 100644
--- a/httemplate/misc/tax-import.cgi
+++ b/httemplate/misc/tax-import.cgi
@@ -6,7 +6,7 @@ Import a CSV file set containing tax rate records.
<% include( '/elements/form-file_upload.html',
'name' => 'TaxRateUpload',
'action' => 'process/tax-import.cgi',
- 'num_files' => 5,
+ 'num_files' => 6,
'fields' => [ 'format', ],
'message' => 'Tax rates imported',
)
@@ -27,13 +27,15 @@ Import a CSV file set containing tax rate records.
</TR>
<% include( '/elements/file-upload.html',
- 'field' => [ 'codefile',
+ 'field' => [ 'geofile',
+ 'codefile',
'plus4file',
'zipfile',
'txmatrix',
'detail',
],
- 'label' => [ 'code filename',
+ 'label' => [ 'geocode filename',
+ 'code filename',
'plus4 filename',
'zip filename',
'txmatrix filename',
diff --git a/httemplate/misc/xmlhttp-cust_main-address_standardize.html b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
index 72fa4a4..3b9e142 100644
--- a/httemplate/misc/xmlhttp-cust_main-address_standardize.html
+++ b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
@@ -50,6 +50,9 @@ if ( $sub eq 'address_standardize' ) {
unless ( $verifier->is_error ) {
+ my $zip = $hash->{Zip5};
+ $zip .= '-'. $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
+
$return = {
%$return,
"new_$pre".'company' => $hash->{FirmName},
@@ -57,7 +60,7 @@ if ( $sub eq 'address_standardize' ) {
"new_$pre".'address2' => $hash->{Address1},
"new_$pre".'city' => $hash->{City},
"new_$pre".'state' => $hash->{State},
- "new_$pre".'zip' => $hash->{Zip5}. '-'. $hash->{Zip4},
+ "new_$pre".'zip' => $zip,
};
my @fields = (qw( company address1 address2 city state zip )); #hmm
diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html
new file mode 100644
index 0000000..9d588d7
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_main-censustract.html
@@ -0,0 +1,105 @@
+<% objToJson($return) %>
+<%init>
+
+my $DEBUG = 0;
+
+my $url='http://www.ffiec.gov/Geocode/default.aspx';
+
+my $sub = $cgi->param('sub');
+
+my $return = {};
+my $error = '';
+
+use LWP::UserAgent;
+use HTTP::Request;
+use HTTP::Request::Common qw( GET POST );
+use HTML::TokeParser;
+
+if ( $sub eq 'censustract' ) {
+
+ my %arg = $cgi->param('arg');
+ warn join('', map "$_: $arg{$_}\n", keys %arg )
+ if $DEBUG;
+
+ my $ua = new LWP::UserAgent;
+ my $res = $ua->request( GET( $url ) );
+
+ warn $res->as_string
+ if $DEBUG > 1;
+
+ unless ($res->code eq '200') {
+
+ $error = $res->message;
+
+ } else {
+
+ my $content = $res->content;
+ my $p = new HTML::TokeParser \$content;
+ my $viewstate;
+ while (my $token = $p->get_tag('input') ) {
+ next unless $token->[1]->{name} eq '__VIEWSTATE';
+ $viewstate = $token->[1]->{value};
+ last;
+ }
+
+ unless ($viewstate) {
+
+ $error = "no __VIEWSTATE found";
+
+ } else {
+
+ my($zip5, $zip4) = split('-',$arg{zip});
+
+ my @ffiec_args = (
+ __VIEWSTATE => $viewstate,
+ ddlbYear => $arg{year},
+ txtAddress => $arg{address},
+ txtCity => $arg{city},
+ ddlbState => $arg{state},
+ txtZipCode => $zip5,
+ btnSearch => 'Search',
+ );
+ warn join("\n", @ffiec_args )
+ if $DEBUG;
+
+ $res = $ua->request( POST( $url, \@ffiec_args ) );
+ warn $res->as_string
+ if $DEBUG > 1;
+
+ unless ($res->code eq '200') {
+
+ $error = $res->message;
+
+ } else {
+
+ my @id = qw( MSACode StateCode CountyCode TractCode );
+ $content = $res->content;
+ $p = new HTML::TokeParser \$content;
+ my $prefix = 'UcGeoResult11_lb';
+ my $compare =
+ sub { my $t=shift; scalar( grep { lc($t) eq lc("$prefix$_")} @id ) };
+
+ while (my $token = $p->get_tag('span') ) {
+ next unless ( $token->[1]->{id} && &$compare( $token->[1]->{id} ) );
+ $token->[1]->{id} =~ /^$prefix(\w+)$/;
+ $return->{lc($1)} = $p->get_trimmed_text("/span");
+ }
+
+ $error = "No census tract found" unless $return->{tractcode};
+ $return->{tractcode} .= ' '
+ unless $error || $JSON::VERSION >= 2; #broken JSON 1 workaround
+
+ } #unless ($res->code eq '200')
+
+ } #unless ($viewstate)
+
+ } #unless ($res->code eq '200')
+
+ $error = "FFIEC Geocoding error: $error" if $error;
+ $return->{'error'} = $error;
+
+ $return;
+
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html
new file mode 100644
index 0000000..e993032
--- /dev/null
+++ b/httemplate/misc/xmlhttp-ping.html
@@ -0,0 +1,20 @@
+<% objToJson($return) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+die "$sub not supported" unless $sub eq 'ping';
+
+my $ip = $cgi->param('arg');
+
+my $ping = new Net::Ping('external', 5);
+$ping->hires(1);
+#my $a=time; warn "pinging\n";
+my ($ret, $duration, $ip2) = $ping->ping($ip);
+#warn "done pinging (". int(time-$a). "s)\n";
+
+my $return = [ $ret, $duration ];
+
+</%init>
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index 9661516..378164e 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -1,58 +1,67 @@
-% my $error = '';
-%
-% my $access_user;
-% if ( grep { $cgi->param($_) !~ /^\s*$/ }
-% qw(_password new_password new_password2)
-% ) {
-%
-% $access_user = qsearchs( 'access_user', {
-% 'username' => getotaker,
-% '_password' => $cgi->param('_password'),
-% } );
-%
-% $error = 'Current password incorrect; password not changed'
-% unless $access_user;
-%
-% $error ||= "New passwords don't match"
-% unless $cgi->param('new_password') eq $cgi->param('new_password2');
-%
-% $error ||= "No new password entered"
-% unless length($cgi->param('new_password'));
-%
-% $access_user->_password($cgi->param('new_password')) unless $error;
-%
-% } else {
-%
-% $access_user = $FS::CurrentUser::CurrentUser;
-%
-% }
-%
-% my %param = $access_user->options;
-%
-% #XXX autogen
-% my @paramlist = qw( menu_position
-% email_address
-% vonage-fromnumber vonage-username vonage-password
-% show_pkgnum show_db_profile save_db_profile
-% height width availHeight availWidth colorDepth
-% );
-%
-% foreach (@paramlist) {
-% scalar($cgi->param($_)) =~ /^[,.\-\@\w]*$/ && next;
-% $error ||= "Illegal value for parameter $_";
-% last;
-% }
-%
-% foreach (@paramlist) {
-% $param{$_} = scalar($cgi->param($_));
-% }
-%
-% $error ||= $access_user->replace( \%param );
-%
% if ( $error ) {
% $cgi->param('error', $error);
-% print $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string );
+<% $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string ) %>
% } else {
<% include('/elements/header.html', 'Preferences updated') %>
<% include('/elements/footer.html') %>
% }
+<%init>
+
+my $error = '';
+my $access_user = '';
+
+if ( grep { $cgi->param($_) !~ /^\s*$/ }
+ qw(_password new_password new_password2)
+ ) {
+
+ $access_user = qsearchs( 'access_user', {
+ 'username' => getotaker,
+ '_password' => $cgi->param('_password'),
+ } );
+
+ $error = 'Current password incorrect; password not changed'
+ unless $access_user;
+
+ $error ||= "New passwords don't match"
+ unless $cgi->param('new_password') eq $cgi->param('new_password2');
+
+ $error ||= "No new password entered"
+ unless length($cgi->param('new_password'));
+
+ $access_user->_password($cgi->param('new_password')) unless $error;
+
+} else {
+
+ $access_user = $FS::CurrentUser::CurrentUser;
+
+}
+
+#well, if you got your password change wrong, you don't get anything else
+#changed right now. but it should be sticky on the form
+unless ( $error ) { # if ($access_user) {
+
+ my %param = $access_user->options;
+
+ #XXX autogen
+ my @paramlist = qw( menu_position default_customer_view
+ email_address
+ vonage-fromnumber vonage-username vonage-password
+ show_pkgnum show_db_profile save_db_profile
+ height width availHeight availWidth colorDepth
+ );
+
+ foreach (@paramlist) {
+ scalar($cgi->param($_)) =~ /^[,.\-\@\w]*$/ && next;
+ $error ||= "Illegal value for parameter $_";
+ last;
+ }
+
+ foreach (@paramlist) {
+ $param{$_} = scalar($cgi->param($_));
+ }
+
+ $error ||= $access_user->replace( \%param );
+
+}
+
+</%init>
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 57e22b3..562ef29 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -31,7 +31,7 @@ Interface
<% ntable("#cccccc",2) %>
<TR>
- <TH>Menu location: </TH>
+ <TH ALIGN="right">Menu location: </TH>
<TD>
<INPUT TYPE="radio" NAME="menu_position" VALUE="left" onClick="document.images['menu_example'].src='../images/menu-left-example.png';" <% $menu_position eq 'left' ? ' CHECKED' : ''%>> Left<BR>
<INPUT TYPE="radio" NAME="menu_position" VALUE="top"onClick="document.images['menu_example'].src='../images/menu-top-example.png';" <% $menu_position eq 'top' ? ' CHECKED' : ''%>> Top <BR>
@@ -39,6 +39,21 @@ Interface
<TD><IMG NAME="menu_example" SRC="../images/menu-<% $menu_position %>-example.png"></TD>
</TR>
+ <TR>
+ <TH ALIGN="right">Default customer view: </TD>
+ <TD COLSPAN=2>
+ <SELECT NAME="default_customer_view">
+% foreach my $view ( keys %customer_views ) {
+% my $selected =
+% $customer_views{$view} eq $curuser->option('default_customer_view')
+% ? 'SELECTED'
+% : '';
+ <OPTION VALUE="<%$customer_views{$view}%>" <%$selected%>><%$view%></OPTION>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
</TABLE>
<BR>
@@ -113,8 +128,21 @@ Vonage integration (see <a href="https://secure.click2callu.com/">Click2Call</a>
my $curuser = $FS::CurrentUser::CurrentUser;
+#false laziness w/view/cust_main.cgi and Conf.pm (cust_main-default_view)
+
+tie my %customer_views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+ 'Tickets' => 'tickets',
+ 'Packages' => 'packages',
+ 'Payment History' => 'payment_history',
+;
+$customer_views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$customer_views{'Jumbo'} = 'jumbo';
+
# XSS via your own preferences? seems unlikely, but nice try anyway...
-( $curuser->option('menu_position') || 'left' )
+( $curuser->option('menu_position') || 'top' )
=~ /^(\w+)$/ or die "illegal menu_position";
my $menu_position = $1;
( $curuser->option('email_address') )
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
new file mode 100755
index 0000000..9102c20
--- /dev/null
+++ b/httemplate/search/477.html
@@ -0,0 +1,155 @@
+<% include( 'elements/search.html',
+ 'title' => 'FCC Form 477 Results',
+ 'html_init' => $html_init,
+ 'name' => 'regions',
+ 'query' => [ @sql_query ],
+ 'count_query' => $count_query,
+ 'order_by' => 'ORDER BY censustract',
+ 'header' => [
+ 'County code',
+ 'Census tract code',
+ 'Upload rate',
+ 'Download rate',
+ 'Technology code',
+ 'Technology code other',
+ 'Quantity',
+ 'Percentage residential',
+ ],
+ 'fields' => [
+ sub { my $row = shift; substr($row->censustract, 2, 3) },
+ sub { my $row = shift; substr($row->censustract, 5) },
+ 'upload',
+ 'download',
+ sub { 7 },
+ sub { '' },
+ 'quantity',
+ sub { my $row = shift; sprintf "%.2f", $row->residential },
+ ],
+ 'links' => [
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ [ $link, $link_suffix ],
+ ],
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List packages');
+
+my %search_hash = ();
+my @sql_query = ();
+
+for ( qw(agentnum magic classnum) ) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+my @column_option = $cgi->param('column_option')
+ if $cgi->param('column_option');
+
+my @row_option = $cgi->param('row_option')
+ if $cgi->param('row_option');
+
+my $where = join(' OR ', map { "num = $_" } grep { /^\d+$/ } @column_option );
+my %column_option_name = $where ?
+ ( map { $_->name => $_->num }
+ qsearch({ 'table' => 'part_pkg_report_option',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE $where",
+ })
+ ) :
+ ( 'all packages' => '' );
+
+$where = join(' OR ', map { "num = $_" } grep { /^\d+$/ } @row_option );
+my %row_option_name = $where ?
+ ( map { $_->name => $_->num }
+ qsearch({ 'table' => 'part_pkg_report_option',
+ 'hashref' => {},
+ 'extra_sql' => "WHERE $where",
+ })
+ ) :
+ ( 'all packages' => '' );
+
+@row_option = map { $row_option_name{$_} } sort keys %row_option_name;
+@column_option = map { $column_option_name{$_} } sort keys %column_option_name;
+
+#$search_hash{row_option} = join(',', @row_option) if @row_option;
+my $html_init = '<H2>Summary</H2>'. include('/elements/table.html');
+ $html_init .= '<TR><TH></TH>';
+foreach my $column ( sort keys %column_option_name ) {
+ $html_init .= "<TH>$column</TH>";
+}
+ $html_init .= "</TR>";
+
+my $rowcount = 1;
+foreach my $row ( sort keys %row_option_name ) {
+
+ $html_init .= "<TR><TH>$row</TH>";
+
+ my $columncount = 2;
+ foreach my $column ( sort keys %column_option_name ) {
+ my @report_option = ();
+ push @report_option, $row_option_name{$row}
+ if $row_option_name{$row};
+ push @report_option, $column_option_name{$column}
+ if $column_option_name{$column};
+ my $report_option = join(',', @report_option) if @report_option;
+
+ my $sql_query = FS::cust_pkg->search_sql(
+ { %search_hash,
+ ($report_option ? ( 'report_option' => $report_option ) : () ),
+ }
+ );
+ my $extracolumns = "$rowcount AS upload, $columncount AS download";
+ my $percent = "100-100*cast(count(cust_main.company) as numeric)/cast(count(*) as numeric) AS residential";
+ $sql_query->{select} = "count(*) AS quantity, $extracolumns, censustract, $percent";
+ $sql_query->{extra_sql} =~ /^(.*)(ORDER BY pkgnum)(.*)$/s
+ or die "couldn't parse extra_sql";
+ $sql_query->{extra_sql} = "$1 GROUP BY censustract $3";
+
+ my $count_sql = delete($sql_query->{'count_query'});
+
+ 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];
+
+ $html_init .= "<TD>$count</TD>";
+ push @sql_query, $sql_query;
+ $columncount++;
+ }
+
+ $html_init .= "</TR>";
+ $rowcount++;
+}
+
+$html_init .= "</TABLE><BR><H2>Details</H2>";
+
+my $count_query = 'SELECT count(*) FROM ( ('.
+ join( ') UNION (',
+ map { my $extra = $_->{extra_sql}; my $addl = $_->{addl_from};
+ "SELECT censustract from cust_pkg $addl $extra";
+ }
+ @sql_query
+ ). ') ) AS foo';
+
+my $link = 'cust_pkg.cgi?'.
+ join(';', map{ "$_=". $search_hash{$_} } keys %search_hash). ';';
+my $link_suffix = sub { my $row = shift;
+ my $result = 'censustract='. $row->censustract. ';';
+ $result .= 'report_option='. @row_option[$row->upload - 1]
+ if @row_option[$row->upload - 1];
+ $result .= 'report_option='. @column_option[$row->download - 1]
+ if @column_option[$row->download - 1];
+ $result;
+ };
+</%init>
diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html
index 852eeba..d1f68c5 100644
--- a/httemplate/search/cdr.html
+++ b/httemplate/search/cdr.html
@@ -10,7 +10,7 @@
'count_query' => $count_query,
'header' => [
'', # checkbox column
- fields('cdr'), #XXX fill in some nice names
+ @header,
],
'fields' => [
sub {
@@ -20,9 +20,10 @@
my $acctid = $cdr->acctid;
qq!<INPUT NAME="acctid$acctid" TYPE="checkbox" VALUE="1">!;
},
- fields('cdr'), #XXX fill in some pretty-print
+ @fields, #XXX fill in some pretty-print
#processing, etc.
],
+ 'links' => \@links,
'html_form' => qq!<FORM NAME="cdrForm" ACTION="$p/misc/cdr.cgi" METHOD="POST">!,
#false laziness w/queue.html
@@ -65,46 +66,87 @@ my $hashref = {};
my @search = ();
###
+# dates
+###
+
+my $str2time_sql = str2time_sql;
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "$str2time_sql calldate) >= $beginning ",
+ "$str2time_sql calldate) <= $ending";
+
+###
+# duration / billsec
+###
+
+push @search, FS::UI::Web::parse_lt_gt($cgi, 'duration');
+push @search, FS::UI::Web::parse_lt_gt($cgi, 'billsec');
+
+#above here things just push @search
+#below here things also have to define $hashref->{} or push @qsearch
+my @qsearch = @search;
+
+###
# freesidestatus
###
if ( $cgi->param('freesidestatus') eq 'NULL' ) {
- my $title = "Unprocessed $title";
+ $title = "Unprocessed $title";
$hashref->{'freesidestatus'} = ''; # Record.pm will take care of it
push @search, "( freesidestatus IS NULL OR freesidestatus = '' )";
} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) {
- my $title = "Processed $title";
+ $title = "Processed $title";
$hashref->{'freesidestatus'} = $1;
push @search, "freesidestatus = '$1'";
}
###
-# dates
+# termpartNstatus
###
-my $str2time_sql = str2time_sql;
+foreach my $param ( grep /^termpart\d+status$/, $cgi->param ) {
-my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
-push @search, "$str2time_sql calldate) >= $beginning ",
- "$str2time_sql calldate) <= $ending";
+ my $status = $cgi->param($param);
-###
-# duration / billsec
-###
+ $param =~ /^termpart(\d+)status$/ or die 'guru meditation 54something';
+ my $termpart = $1;
-push @search, FS::UI::Web::parse_lt_gt($cgi, 'duration');
-push @search, FS::UI::Web::parse_lt_gt($cgi, 'billsec');
+ my $search = '';
+ if ( $status eq 'NULL' ) {
+
+ #false lazienss w/cdr_termination.pm (i should be a part_termination method)
+ my $where_term =
+ "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart ) ";
+ #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
+ $search =
+ "NOT EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
+
+ } elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) {
+
+ #false lazienss w/cdr_termination.pm (i should be a part_termination method)
+ my $where_term =
+ "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart AND status = '$1' ) ";
+ #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )";
+ $search =
+ "EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )";
+
+ }
+
+ if ( $search ) {
+ push @search, $search;
+ push @qsearch, $search;
+ }
+
+}
###
# src/dest/charged_party
###
-my @qsearch = @search;
-
if ( $cgi->param('src') =~ /^\s*([\d\-\+\ ]+)\s*$/ ) {
( my $src = $1 ) =~ s/\D//g;
$hashref->{'src'} = $src;
@@ -117,15 +159,23 @@ if ( $cgi->param('dst') =~ /^\s*([\d\-\+ ]+)\s*$/ ) {
push @search, "dst = '$dst'";
}
+if ( $cgi->param('dcontext') =~ /^\s*(.+)\s*$/ ) {
+ my $dcontext = $1;
+ $hashref->{'dcontext'} = $dcontext;
+ push @search, "dcontext = '$dcontext'";
+}
+
if ( $cgi->param('charged_party') =~ /^\s*([\d\-\+\ ]+)\s*$/ ) {
( my $charged_party = $1 ) =~ s/\D//g;
#$hashref->{'charged_party'} = $charged_party;
#push @search, "charged_party = '$charged_party'";
#XXX countrycode
- push @search, " ( charged_party = '$charged_party'
- OR charged_party = '1$charged_party' ) ";
- push @qsearch, " ( charged_party = '$charged_party'
- OR charged_party = '1$charged_party' ) ";
+
+ my $search = " ( charged_party = '$charged_party'
+ OR charged_party = '1$charged_party' ) ";
+
+ push @search, $search;
+ push @qsearch, $search;
}
###
@@ -144,6 +194,23 @@ if ( $cgi->param('cdrbatch') ne '__ALL__' ) {
}
###
+# acctid
+###
+
+if ( $cgi->param('acctid') =~ /\d/ ) {
+ my $acctid = $cgi->param('acctid');
+ $acctid =~ s/\r\n/\n/g; #browsers?
+ my @acctid = map { /^\s*(\d+)\s*$/ or die "guru meditation #4"; $1; }
+ grep { /^\s*(\d+)\s*$/ }
+ split(/\n/, $acctid);
+ if ( @acctid ) {
+ my $search = 'acctid IN ( '. join(',', @acctid). ' )';
+ push @qsearch, $search;
+ push @search, $search;
+ }
+}
+
+###
# finish it up
###
@@ -156,4 +223,53 @@ my $qsearch = join(' AND ', @qsearch);
$qsearch = ( scalar(keys %$hashref) ? ' AND ' : ' WHERE ' ) . $qsearch
if $qsearch;
+###
+# display fields
+###
+
+my %header = %{ FS::cdr->table_info->{'fields'} };
+
+my @first = qw( acctid calldate clid charged_party src dst dcontext );
+my %first = map { $_=>1 } @first;
+
+my @fields = ( @first, grep !$first{$_}, fields('cdr') );
+
+if ( $cgi->param('show') ) {
+ @fields = grep $cgi->param("show_$_"), @fields;
+}
+
+my @header = map {
+ if ( exists($header{$_}) ) {
+ $header{$_};
+ } else {
+ my $header = $_;
+ $header =~ s/\_/ /g; #//wtf
+ ucfirst($header);
+ }
+ } @fields;
+
+my $date_sub_factory = sub {
+ my $column = shift;
+ sub {
+ #my $cdr = shift;
+ my $date = shift->$column();
+ $date ? time2str( '%Y-%m-%d %T', $date ) : ''; #config time2str format?
+ };
+};
+
+my %fields = (
+ #any other formatters?
+ map { $_ => &{ $date_sub_factory }($_) } qw( startdate answerdate enddate )
+);
+
+my %links = (
+ 'svcnum' =>
+ sub { $_[0]->svcnum ? [ $p.'view/svc_phone.cgi?', 'svcnum' ] : ''; },
+);
+
+@fields = map { exists($fields{$_}) ? $fields{$_} : $_ } @fields;
+
+ #checkbox column
+my @links = ( '', map { exists($links{$_}) ? $links{$_} : '' } @fields );
+
</%init>
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 89901ac..52f59de 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -8,7 +8,10 @@
'#',
'Description',
'Setup charge',
- 'Recurring charge',
+ ( $use_usage eq 'usage'
+ ? 'Usage charge'
+ : 'Recurring charge'
+ ),
'Invoice',
'Date',
FS::UI::Web::cust_header(),
@@ -16,13 +19,23 @@
'fields' => [
'billpkgnum',
sub { $_[0]->pkgnum > 0
- ? $_[0]->get('pkg')
- : $_[0]->get('itemdesc')
+ ? $_[0]->get('pkg') # possibly use override.pkg
+ : $_[0]->get('itemdesc') # but i think this correct
},
#strikethrough or "N/A ($amount)" or something these when
# they're not applicable to pkg_tax search
sub { sprintf($money_char.'%.2f', shift->setup ) },
- sub { sprintf($money_char.'%.2f', shift->recur ) },
+ sub { my $row = shift;
+ my $value = 0;
+ if ( $use_usage eq 'recurring' ) {
+ $value = $row->recur - $row->usage;
+ } elsif ( $use_usage eq 'usage' ) {
+ $value = $row->usage;
+ } else {
+ $value = $row->recur;
+ }
+ sprintf($money_char.'%.2f', $value );
+ },
'invnum',
sub { time2str('%b %d %Y', shift->_date ) },
\&FS::UI::Web::cust_fields,
@@ -83,32 +96,66 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.agentnum = $1";
}
+#classnum
+# not specified: all classes
+# 0: empty class
+# N: classnum
+my $use_override = $cgi->param('use_override');
if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ my $comparison = '';
if ( $1 == 0 ) {
- push @where, "classnum IS NULL";
+ $comparison = "IS NULL";
} else {
- push @where, "classnum = $1";
+ $comparison = "= $1";
+ }
+
+ if ( $use_override ) {
+ push @where, "(
+ part_pkg.classnum $comparison AND pkgpart_override IS NULL OR
+ override.classnum $comparison AND pkgpart_override IS NOT NULL
+ )";
+ } else {
+ push @where, "part_pkg.classnum $comparison";
}
}
-#sub _where {
-# my $table = shift;
-# my $prefix = @_ ? shift : '';
-# "
-# ( cust_main_county.county = $table.${prefix}.county
-# OR ( cust_main_county.county IS NULL AND $table.${prefix}.county = '' )
-# OR ( cust_main_county.county = '' AND $table.${prefix}.county IS NULL)
-# OR ( cust_main_county.county IS NULL AND $table.${prefix}.county IS NULL)
-# )
-# AND ( cust_main_county.state = $table.${prefix}.state
-# OR ( cust_main_county.state IS NULL AND $table.${prefix}.state = '' )
-# OR ( cust_main_county.state = '' AND $table.${prefix}.state IS NULL )
-# OR ( cust_main_county.state IS NULL AND $table.${prefix}.state IS NULL )
-# )
-# AND cust_main_county.country = $table.${prefix}.country
-# ";
-#
-#}
+if ( $cgi->param('taxclass')
+ && ! $cgi->param('istax') #no part_pkg.taxclass in this case
+ #(should we save a taxclass or a link to taxnum
+ # in cust_bill_pkg or something like
+ # cust_bill_pkg_tax_location?)
+ )
+{
+
+ #override taxclass when use_override is specified? probably
+ #if ( $use_override ) {
+ #
+ # push @where,
+ # ' ( '. join(' OR ',
+ # map {
+ # ' ( part_pkg.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NULL '.
+ # ' OR '.
+ # ' override.taxclass = '. dbh->quote($_).
+ # ' AND pkgpart_override IS NOT NULL '.
+ # ' ) '
+ # }
+ # $cgi->param('taxclass')
+ # ).
+ # ' ) ';
+ #
+ #} else {
+
+ push @where,
+ ' ( '. join(' OR ',
+ map ' part_pkg.taxclass = '.dbh->quote($_),
+ $cgi->param('taxclass')
+ ).
+ ' ) ';
+
+ #}
+
+}
if ( $cgi->param('out') ) {
@@ -143,17 +190,49 @@ if ( $cgi->param('out') ) {
}
-} elsif ( $cgi->param('country' ) ) {
+} elsif ( $cgi->param('country') ) {
- my %ph = map { $_ => dbh->quote( $cgi->param($_) ) }
- qw( county state country );
+ my @counties = $cgi->param('county');
+
+ if ( scalar(@counties) > 1 ) {
- my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
- while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
- $loc_sql =~ s/\?/$ph{shift(@param)}/e;
- }
+ #hacky, could be more efficient. care if it is ever used for more than the
+ # tax-report_groups filtering kludge
+
+ my $locs_sql =
+ ' ( '. join(' OR ', map {
+
+ my %ph = ( 'county' => dbh->quote($_),
+ map { $_ => dbh->quote( $cgi->param($_) ) }
+ qw( state country )
+ );
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ $loc_sql;
- push @where, $loc_sql;
+ } @counties
+
+ ). ' ) ';
+
+ push @where, $locs_sql;
+
+ } else {
+
+ my %ph = map { $_ => dbh->quote( $cgi->param($_) ) }
+ qw( county state country );
+
+ my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
+ while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
+ $loc_sql =~ s/\?/$ph{shift(@param)}/e;
+ }
+
+ push @where, $loc_sql;
+
+ }
if ( $cgi->param('istax') ) {
if ( $cgi->param('taxname') ) {
@@ -168,13 +247,6 @@ if ( $cgi->param('out') ) {
#warn "neither nottax nor istax parameters specified";
}
- push @where, ' taxclass = '. dbh->quote( $cgi->param('taxclass') )
- if $cgi->param('taxclass')
- && ! $cgi->param('istax'); #no part_pkg.taxclass in this case
- #(should we save a taxclass or a link to taxnum
- # in cust_bill_pkg or something like
- # cust_bill_pkg_tax_location?)
-
if ( $cgi->param('taxclassNULL') ) {
my %hash = ( 'country' => scalar($cgi->param('country')) );
@@ -189,19 +261,72 @@ if ( $cgi->param('out') ) {
}
+} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+
+ # this should really be shoved out to FS::cust_pkg->location_sql or something
+ # along with the code in report_newtax.cgi
+
+ my %pn = (
+ 'county' => 'tax_rate_location.county',
+ 'state' => 'tax_rate_location.state',
+ 'city' => 'tax_rate_location.city',
+ 'locationtaxid' => 'cust_bill_pkg_tax_rate_location.locationtaxid',
+ );
+
+ my %ph = map { ( $pn{$_} => dbh->quote( $cgi->param($_) || '' ) ) }
+ qw( county state city locationtaxid );
+
+ push @where,
+ join( ' AND ', map { "( $_ = $ph{$_} OR $ph{$_} = '' AND $_ IS NULL)" }
+ keys %ph
+ );
+
}
-if ($cgi->param('itemdesc')) {
- if ($cgi->param('itemdesc') eq 'Tax') {
+if ( $cgi->param('itemdesc') ) {
+ if ( $cgi->param('itemdesc') eq 'Tax' ) {
push @where, "(itemdesc='Tax' OR itemdesc is null)";
- }else{
+ } else {
push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc'));
}
}
+
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) {
+ my ( $group_op, $group_value ) = ( $1, $2 );
+ if ( $group_op eq '=' ) {
+ #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
+ push @where, 'itemdesc = '. dbh->quote($group_value);
+ } elsif ( $group_op eq '!=' ) {
+ push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
+ } else {
+ die "guru meditation #00de: group_op $group_op\n";
+ }
+
+}
+
push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax');
push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax');
-push @where, " tax = 'Y' " if $cgi->param('cust_tax');
+if ( $cgi->param('cust_tax') ) {
+ #false laziness -ish w/report_tax.cgi
+ my $cust_exempt;
+ if ( $cgi->param('taxname') ) {
+ my $q_taxname = dbh->quote($cgi->param('taxname'));
+ $cust_exempt =
+ "( tax = 'Y'
+ OR EXISTS ( SELECT 1 FROM cust_main_exemption
+ WHERE cust_main_exemption.custnum = cust_main.custnum
+ AND cust_main_exemption.taxname = $q_taxname )
+ )
+ ";
+ } else {
+ $cust_exempt = " tax = 'Y' ";
+ }
+
+ push @where, $cust_exempt;
+}
+
+my $use_usage = $cgi->param('use_usage');
my $count_query;
if ( $cgi->param('pkg_tax') ) {
@@ -267,8 +392,15 @@ if ( $cgi->param('pkg_tax') ) {
} else {
- $count_query =
- "SELECT COUNT(*), SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)";
+ $count_query = "SELECT COUNT(*), ";
+
+ if ( $use_usage eq 'recurring' ) {
+ $count_query .= "SUM(setup + recur - usage)";
+ } elsif ( $use_usage eq 'usage' ) {
+ $count_query .= "SUM(usage)";
+ } else {
+ $count_query .= "SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)";
+ }
}
@@ -282,7 +414,9 @@ my $join_pkg;
if ( $cgi->param('nottax') ) {
$join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart ) ';
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override
+ ON pkgpart_override = override.pkgpart ';
$join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) '
if $conf->exists('tax-pkg_address');
@@ -295,6 +429,10 @@ if ( $cgi->param('nottax') ) {
#quelle kludge, false laziness w/report_tax.cgi
$where =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g;
+ } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+ $join_pkg .=
+ ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
+ ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
}
} else {
@@ -307,7 +445,17 @@ if ( $cgi->param('nottax') ) {
}
-$count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where";
+if ($use_usage) {
+ $count_query .=
+ " FROM (SELECT cust_bill_pkg.setup, cust_bill_pkg.recur,
+ ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_bill_pkg_detail
+ WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_detail.billpkgnum
+ ) AS usage FROM cust_bill_pkg $join_cust $join_pkg $where
+ ) AS countquery";
+} else {
+ $count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where";
+}
+warn "count_query is $count_query\n";
my @select = (
'cust_bill_pkg.*',
diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html
index d55b5c6..f8cf6b2 100644
--- a/httemplate/search/cust_event.html
+++ b/httemplate/search/cust_event.html
@@ -75,29 +75,34 @@ my $status_sub = sub {
my $part_event = $cust_event->part_event;
- if ( $part_event->eventtable eq 'cust_bill' && $part_event->templatename ) {
- my $alt_templatename = $part_event->templatename;
- my $alt_link = "$alt_templatename-". $cust_event->tablenum;
+ if ( $part_event->eventtable eq 'cust_bill'
+ && ( $part_event->templatename || $part_event->option('notice_name') )
+ )
+ {
+ my $link = 'invnum='. $cust_event->tablenum;
+ $link .= ';template='. uri_escape($part_event->templatename)
+ if $part_event->templatename;
+ $link .= ';notice_name='. uri_escape($part_event->option('notice_name'))
+ if $part_event->option('notice_name');
my $conf = new FS::Conf;
my $cust_bill = $cust_event->cust_X;
$status .= qq{
- ( <A HREF="${p}view/cust_bill.cgi?$alt_link">view</A>
- | <A HREF="${p}view/cust_bill-pdf.cgi?$alt_link.pdf">view
- typeset</A>
- | <A HREF="${p}misc/print-invoice.cgi?$alt_link">re-print</A>
+ ( <A HREF="${p}view/cust_bill.cgi?$link">view</A>
+ | <A HREF="${p}view/cust_bill-pdf.cgi?$link">view&nbsp;typeset</A>
+ | <A HREF="${p}misc/send-invoice.cgi?method=print;$link">re-print</A>
};
if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
$status .= qq{
- | <A HREF="${p}misc/email-invoice.cgi?$alt_link">re-email</A>
+ | <A HREF="${p}misc/send-invoice.cgi?method=email;$link">re-email</A>
};
}
if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
$status .= qq{
- | <A HREF="${p}misc/fax-invoice.cgi?$alt_link">re-fax</A>
+ | <A HREF="${p}misc/send-invoice.cgi?method=fax;$link">re-fax</A>
}
}
@@ -124,7 +129,12 @@ my $trigger_link = sub {
my $eventtable = $cust_event->eventtable;
if ( $eventtable eq 'cust_pkg' ) {
my $custnum = $cust_event->cust_main_custnum;
- [ "${p}view/cust_main.cgi?$custnum#cust_pkg", 'tablenum' ];
+ my $show = $FS::CurrentUser::CurrentUser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+ my $pkgnum = $cust_event->tablenum;
+ my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+ [ "${p}view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#cust_pkg", 'tablenum' ];
} else {
[ "${p}view/$eventtable.cgi?", 'tablenum' ];
}
@@ -142,61 +152,24 @@ die "access denied"
|| $cgi->param('invnum') =~ /^(\d+)$/
|| $cgi->param('pkgnum') =~ /^(\d+)$/
);
-
-my $title = $cgi->param('failed')
- ? 'Failed billing events'
- : 'Billing events';
+my $title = $cgi->param('failed') ? 'Failed billing events' : 'Billing events';
-my @search = ();
+my %search = ();
-if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
- push @search, "cust_main.agentnum = $1";
- #my $agent = qsearchs('agent', { 'agentnum' => $1 } );
- #die "unknown agentnum $1" unless $agent;
+my @scalars = qw ( agentnum custnum invnum pkgnum failed );
+for my $param ( @scalars ) {
+ $search{$param} = scalar( $cgi->param($param) )
+ if $cgi->param($param);
}
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
-push @search, "cust_event._date >= $beginning",
- "cust_event._date <= $ending";
+$search{'beginning'} = $beginning;
+$search{'ending'} = $ending;
-if ( $cgi->param('failed') ) {
- push @search, "statustext != ''",
- "statustext IS NOT NULL",
- "statustext != 'N/A'";
-}
-
-#if ( $cgi->param('part_event.payby') =~ /^(\w+)$/ ) {
-# push @search, "part_event.payby = '$1'";
-#}
-
-if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
- push @search, "cust_main.custnum = '$1'";
-}
-if ( $cgi->param('invnum') =~ /^(\d+)$/ ) {
- push @search, "part_event.eventtable = 'cust_bill'",
- "tablenum = '$1'";
-}
-if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
- push @search, "part_event.eventtable = 'cust_pkg'",
- "tablenum = '$1'";
-}
-
-#here is the agent virtualization
-push @search, $curuser->agentnums_sql( 'table' => 'cust_main' );
-
-my $where = 'WHERE '. join(' AND ', @search );
+my $where = ' WHERE '. FS::cust_event->search_sql( \%search );
-my $join = "
- JOIN part_event USING ( eventpart )
- LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = invnum )
- LEFT JOIN cust_pkg ON ( eventtable = 'cust_pkg' AND tablenum = pkgnum )
- LEFT JOIN cust_main ON ( ( eventtable = 'cust_main' AND tablenum = cust_main.custnum )
- OR ( eventtable = 'cust_bill' AND cust_bill.custnum = cust_main.custnum )
- OR ( eventtable = 'cust_pkg' AND cust_pkg.custnum = cust_main.custnum )
- )
-";
- #'LEFT JOIN cust_main USING ( custnum ) ';
+my $join = FS::cust_event->join_sql();
my $sql_query = {
'table' => 'cust_event',
@@ -217,22 +190,24 @@ my $count_sql = "SELECT COUNT(*) FROM cust_event $join $where";
my $conf = new FS::Conf;
-my $failed = $cgi->param('failed');
+my @params = ( @scalars, qw( beginning ending ) );
my $html_init = join("\n", map {
( my $action = $_ ) =~ s/_$//;
include('/elements/progress-init.html',
$_.'form',
- [ 'action', 'beginning', 'ending', 'failed' ],
+ [ 'action', @params ],
"../misc/${_}events.cgi",
{ 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but...
$_, #key
),
qq!<FORM NAME="${_}form">!,
qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though
- qq!<INPUT TYPE="hidden" NAME="beginning" VALUE="$beginning">!,
- qq!<INPUT TYPE="hidden" NAME="ending" VALUE="$ending">!,
- qq!<INPUT TYPE="hidden" NAME="failed" VALUE="$failed">!,
+ ( map { my $value = encode_entities( $search{$_} );
+ qq(<INPUT TYPE="hidden" NAME="$_" VALUE="$value">);
+ }
+ @params #keys %search
+ ),
qq!</FORM>!
} qw( print_ email_ fax_ ) ).
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
index 36e4374..e65dc71 100755
--- a/httemplate/search/cust_main.cgi
+++ b/httemplate/search/cust_main.cgi
@@ -1,5 +1,7 @@
+%my $curuser = $FS::CurrentUser::CurrentUser;
+%
%die "access denied"
-% unless $FS::CurrentUser::CurrentUser->access_right('List customers');
+% unless $curuser->access_right('List customers');
%
%my $conf = new FS::Conf;
%my $maxrecords = $conf->config('maxsearchrecordsperpage');
@@ -483,14 +485,17 @@
%# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } );
% my $part_pkg = $_->part_pkg;
%
-% my $pkg = $part_pkg->pkg;
-% my $comment = $part_pkg->comment;
-% my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum";
+% my $pkg_comment = $part_pkg->pkg_comment(nopkgpart => 1);
+% my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+% ? ''
+% : ';show=packages';
+% my $frag = "cust_pkg$pkgnum"; #hack for IE ignoring real #fragment
+% my $pkgview = "${p}view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag";
% my @cust_svc = @{shift @lol_cust_svc};
% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } );
% my $rowspan = scalar(@cust_svc) || 1;
%
-% print $n1, qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!;
+% print $n1, qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg_comment</FONT></A></TD>!;
%
% my($n2)='';
% foreach my $cust_svc ( @cust_svc ) {
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
index 3282f0f..f098fd3 100755
--- a/httemplate/search/cust_main.html
+++ b/httemplate/search/cust_main.html
@@ -43,9 +43,12 @@ my %search_hash = ();
#$search_hash{'query'} = $cgi->keywords;
#scalars
-for my $param (qw(
+my @scalars = qw (
agentnum status cancelled_pkgs cust_fields flattened_pkgs custbatch
-)) {
+ no_censustract
+);
+
+for my $param ( @scalars ) {
$search_hash{$param} = scalar( $cgi->param($param) )
if $cgi->param($param);
}
@@ -97,7 +100,7 @@ my $menubar = [];
if ( $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices') ) {
- my $uri = new URI::URL;
+ my $uri = new URI;
$uri->query_form( \%search_hash );
my $query = $uri->query;
diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi
index 1576963..2056876 100755
--- a/httemplate/search/cust_pay_batch.cgi
+++ b/httemplate/search/cust_pay_batch.cgi
@@ -132,6 +132,9 @@ if ( $pay_batch ) {
|| ( $pay_batch->status eq 'I'
&& $FS::CurrentUser::CurrentUser->access_right('Reprocess batches')
)
+ || ( $pay_batch->status eq 'R'
+ && $FS::CurrentUser::CurrentUser->access_right('Redownload resolved batches')
+ )
) {
$html_init .= qq!<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">!;
if ( $fixed ) {
@@ -144,6 +147,7 @@ if ( $pay_batch ) {
qq!<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>!.
qq!<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>!.
qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
+ qq!<OPTION VALUE="paymentech">Chase Paymentech</OPTION>!.
qq!</SELECT>!;
}
$html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR>!;
@@ -168,6 +172,7 @@ if ( $pay_batch ) {
qq!<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>!.
qq!<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>!.
qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!.
+ qq!<OPTION VALUE="paymentech">Chase Paymentech</OPTION>!.
qq!</SELECT><BR>!;
}
$html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum">!;
diff --git a/httemplate/search/cust_pay_void.html b/httemplate/search/cust_pay_void.html
new file mode 100755
index 0000000..431bb2c
--- /dev/null
+++ b/httemplate/search/cust_pay_void.html
@@ -0,0 +1,13 @@
+<% include( 'elements/cust_pay_or_refund.html',
+ 'thing' => 'pay_void',
+ 'amount_field' => 'paid',
+ 'name_singular' => 'voided payment',
+ 'name_verb' => 'voided', # 'paid',
+ 'disable_by' => 1, #showing original not voiding otaker
+ 'addl_header' => [ 'Void Date', ], # 'Void Reason' ],
+ 'addl_fields' => [
+ sub { time2str('%b %d %Y', shift->void_date ) },
+ #'reason',
+ ],
+ )
+%>
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
index bd4a946..f03bbc2 100755
--- a/httemplate/search/cust_pkg.cgi
+++ b/httemplate/search/cust_pkg.cgi
@@ -10,6 +10,8 @@
'Package',
'Class',
'Status',
+ 'Setup',
+ 'Base Recur',
'Freq.',
'Setup',
'Last bill',
@@ -33,6 +35,15 @@
},
'classname',
sub { ucfirst(shift->status); },
+ sub { sprintf( $money_char.'%.2f',
+ shift->part_pkg->option('setup_fee'),
+ );
+ },
+ sub { my $c = shift;
+ sprintf( $money_char.'%.2f',
+ $c->part_pkg->base_recur($c)
+ );
+ },
sub { #shift->part_pkg->freq_pretty;
#my $part_pkg = $part_pkg{shift->pkgpart};
@@ -99,13 +110,15 @@
'',
'',
'',
+ '',
+ '',
FS::UI::Web::cust_colors(),
'',
],
- 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '',
+ 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '',
FS::UI::Web::cust_styles() ],
'size' => [ '', '', '', '', '-1' ],
- 'align' => 'rrlcclrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
+ 'align' => 'rrlccrrlrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
'links' => [
$link,
$link,
@@ -121,6 +134,8 @@
'',
'',
'',
+ '',
+ '',
( map { $_ ne 'Cust. Status' ? $clink : '' }
FS::UI::Web::cust_header(
$cgi->param('cust_fields')
@@ -133,19 +148,36 @@
%>
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+ unless $curuser->access_right('List packages');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {});
- my %search_hash = ();
+my %search_hash = ();
+
+#some false laziness w/misc/bulk_change_pkg.cgi
- $search_hash{'query'} = $cgi->keywords;
+$search_hash{'query'} = $cgi->keywords;
- for my $param (qw(agentnum magic status classnum pkgpart)) {
- $search_hash{$param} = $cgi->param($param)
- if $cgi->param($param);
- }
+for (qw( agentnum magic status classnum custom )) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+}
+
+$search_hash{'pkgpart'} = [ $cgi->param('pkgpart') ];
+
+for my $param ( qw(censustract) ) {
+ $search_hash{$param} = $cgi->param($param) || ''
+ if ( grep { /$param/ } $cgi->param );
+}
+
+my @report_option = $cgi->param('report_option')
+ if $cgi->param('report_option');
+$search_hash{report_option} = join(',', @report_option) if @report_option;
###
# parse dates
@@ -175,8 +207,17 @@ foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
my $sql_query = FS::cust_pkg->search_sql(\%search_hash);
my $count_query = delete($sql_query->{'count_query'});
+my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/
+ ? ''
+ : ';show=packages';
+
my $link = sub {
- [ "${p}view/cust_main.cgi?".shift->custnum.'#cust_pkg', 'pkgnum' ];
+ my $self = shift;
+ my $frag = 'cust_pkg'. $self->pkgnum; #hack for IE ignoring real #fragment
+ [ "${p}view/cust_main.cgi?custnum=".$self->custnum.
+ "$show;fragment=$frag#cust_pkg",
+ 'pkgnum'
+ ];
};
my $clink = sub {
diff --git a/httemplate/search/cust_tax_adjustment.html b/httemplate/search/cust_tax_adjustment.html
new file mode 100644
index 0000000..9254765
--- /dev/null
+++ b/httemplate/search/cust_tax_adjustment.html
@@ -0,0 +1,54 @@
+<% include( 'elements/search.html',
+ 'title' => $title,
+ 'name_singular' => 'tax adjustment',
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'header' => [ 'Tax', 'Amount', 'Comment', 'Invoice' ],
+ 'fields' => [ 'taxname',
+ sub { $money_char. shift->amount },
+ 'comment',
+ sub { my $l = shift->cust_bill_pkg;
+ $l ? '#'.$l->invnum : '';
+ },
+ ],
+ 'links' => [ '', '', '', $ilink ],
+ )
+%>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer tax adjustment');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $count_query = 'SELECT COUNT(*) FROM cust_tax_adjustment';
+
+my $hashref = {};
+
+my $custnum = '';
+my $cust_main = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ $hashref->{'custnum'} = $custnum;
+ $count_query .= " WHERE custnum = $custnum ";
+}
+
+my $title = 'Tax adjustments';
+$title .= ' for '. $cust_main->name if $cust_main;
+
+my $query = { 'table' => 'cust_tax_adjustment',
+ 'hashref' => $hashref,
+ };
+
+my $ilink = [ $p.'view/cust_bill.cgi?', sub { my $l = shift->cust_bill_pkg;
+ $l ? $l->invnum : 'EXCEPTION';
+ }
+ ];
+
+#XXX would be nice to list customer fields on the report too, if we ever need
+# to link to here without a custnum (i'm sure we will, eventually...)
+
+</%init>
diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html
new file mode 100644
index 0000000..cc01492
--- /dev/null
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -0,0 +1,219 @@
+<%doc>
+
+Example:
+
+ include( 'elements/cust_main_dayranges.html',
+ 'title' => 'Accounts Receivable Aging Summary',
+ 'range_sub' => $mysub,
+ )
+
+ my $mysub = sub {
+ my( $start, $end ) = @_;
+
+ "SQL EXPRESSION BASED ON $start AND $end";
+ };
+
+</%doc>
+<% include( 'search.html',
+ 'name' => 'customers',
+ 'query' => $sql_query,
+ 'count_query' => $count_sql,
+ 'header' => [
+ FS::UI::Web::cust_header(),
+ '0-30',
+ '30-60',
+ '60-90',
+ '90+',
+ 'Total',
+ ],
+ 'footer' => [
+ 'Total',
+ ( map '',
+ ( 1 ..
+ scalar(FS::UI::Web::cust_header()-1)
+ )
+ ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_0_30'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_30_60'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_60_90'} ),
+ sprintf( $money_char.'%.2f',
+ $row->{'rangecol_90_0'} ),
+ sprintf( '<b>'. $money_char.'%.2f'. '</b>',
+ $row->{'rangecol_0_0'} ),
+ ],
+ 'fields' => [
+ \&FS::UI::Web::cust_fields,
+ format_rangecol('0_30'),
+ format_rangecol('30_60'),
+ format_rangecol('60_90'),
+ format_rangecol('90_0'),
+ format_rangecol('0_0'),
+ ],
+ 'links' => [
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ '',
+ '',
+ '',
+ '',
+ '',
+ ],
+ #'align' => 'rlccrrrrr',
+ 'align' => FS::UI::Web::cust_aligns(). 'rrrrr',
+ #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ],
+ #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ],
+ 'size' => [ ( map '', FS::UI::Web::cust_header() ),
+ #'-1', '', '', '', '', '', ],
+ '', '', '', '', '', ],
+ 'style' => [ FS::UI::Web::cust_styles(),
+ #'b', '', '', '', '', 'b', ],
+ '', '', '', '', 'b', ],
+ 'color' => [
+ FS::UI::Web::cust_colors(),
+ '',
+ '',
+ '',
+ '',
+ '',
+ ],
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+
+#actually need to auto-generate other things too for a passed-in ranges to work
+my $ranges = $opt{'ranges'} ? delete($opt{'ranges'}) : [
+ [ 0, 30 ],
+ [ 30, 60 ],
+ [ 60, 90 ],
+ [ 90, 0 ],
+ [ 0, 0 ],
+];
+
+my $range_sub = delete($opt{'range_sub'}); #or die
+
+#my $range_cols = join(',', map &{$range_sub}( @$_ ), @ranges );
+my $range_cols = join(',', map call_range_sub($range_sub, @$_ ), @$ranges );
+
+my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql;
+
+my $active_sql = FS::cust_pkg->active_sql;
+my $inactive_sql = FS::cust_pkg->inactive_sql;
+my $suspended_sql = FS::cust_pkg->suspended_sql;
+my $cancelled_sql = FS::cust_pkg->cancelled_sql;
+
+my $packages_cols = <<END;
+ ( $select_count_pkgs ) AS num_pkgs_sql,
+ ( $select_count_pkgs AND $active_sql ) AS active_pkgs,
+ ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs,
+ ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs,
+ ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs
+END
+
+my @where = ();
+
+unless ( $cgi->param('all_customers') ) {
+
+ my $days = 0;
+ if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) {
+ $days = $1;
+ }
+
+ push @where,
+ call_range_sub($range_sub, $days, 0, 'no_as'=>1). ' > 0'; # != 0';
+}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ my $agentnum = $1;
+ push @where, "agentnum = $agentnum";
+}
+
+#status (false laziness w/cust_main::search_sql
+
+#prospect active inactive suspended cancelled
+if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) {
+ my $method = $cgi->param('status'). '_sql';
+ #push @where, $class->$method();
+ push @where, FS::cust_main->$method();
+}
+
+#here is the agent virtualization
+push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = join(' AND ', @where);
+$where = "WHERE $where" if $where;
+
+my $count_sql = "select count(*) from cust_main $where";
+
+my $sql_query = {
+ 'table' => 'cust_main',
+ 'hashref' => {},
+ 'select' => join(',',
+ #'cust_main.*',
+ 'custnum',
+ $range_cols,
+ $packages_cols,
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $where,
+ 'order_by' => "order by coalesce(lower(company), ''), lower(last)",
+};
+
+my $total_sql =
+ "SELECT ".
+ join(',', map call_range_sub( $range_sub, @$_, 'sum'=>1 ), @$ranges).
+ " FROM cust_main $where";
+
+my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
+$total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr;
+my $row = $total_sth->fetchrow_hashref();
+
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+</%init>
+<%once>
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+#Example:
+#
+# my $balance = balance(
+# $start, $end,
+# 'no_as' => 1, #set to true when using in a WHERE clause (supress AS clause)
+# #or 0 / omit when using in a SELECT clause as a column
+# # ("AS balance_$start_$end")
+# 'sum' => 1, #set to true to get a SUM() of the values, for totals
+#
+# #obsolete? options for totals (passed to cust_main::balance_date_sql)
+# 'total' => 1, #set to true to remove all customer comparison clauses
+# 'join' => $join, #JOIN clause
+# 'where' => \@where, #WHERE clause hashref (elements "AND"ed together)
+# )
+
+sub call_range_sub {
+ my($range_sub, $start, $end, %opt) = @_;
+
+ my $as = $opt{'no_as'} ? '' : " AS rangecol_${start}_$end";
+
+ my $sql = &{$range_sub}( $start, $end ); #%opt?
+
+ $sql = "SUM($sql)" if $opt{'sum'};
+
+ $sql.$as;
+
+}
+
+sub format_rangecol { #closures help alot
+ my $range = shift;
+ sub { sprintf( $money_char.'%.2f', shift->get("rangecol_$range") ) };
+}
+
+</%once>
diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
index add8427..874bd8a 100755
--- a/httemplate/search/elements/cust_pay_or_refund.html
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -117,7 +117,6 @@ if ( $cgi->param('magic') ) {
my $orderby;
if ( $cgi->param('magic') eq '_date' ) {
-
if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
push @search, "agentnum = $1"; # $search{'agentnum'} = $1;
my $agent = qsearchs('agent', { 'agentnum' => $1 } );
@@ -219,6 +218,13 @@ if ( $cgi->param('magic') ) {
push @search, "_date >= $beginning ",
"_date <= $ending";
+ if ( $thing eq 'pay_void' ) {
+ my($v_beginning, $v_ending) =
+ FS::UI::Web::parse_beginning_ending($cgi, 'void');
+ push @search, "void_date >= $v_beginning ",
+ "void_date <= $v_ending";
+ }
+
push @search, FS::UI::Web::parse_lt_gt($cgi, $amount_field );
$orderby = '_date';
@@ -288,7 +294,9 @@ if ( ( $curuser->access_right('View invoices') #XXX for now
&& ! $opt{'disable_link'}
)
{
- $link = [ "${p}view/cust_$thing.html?${thing}num=", $thing.'num' ]
+ my $key = $thing eq 'pay_void' ? 'paynum' : $thing.'num';
+ my $q = ( $thing eq 'pay_void' ? 'void=1;' : '' ). "$key=";
+ $link = [ "${p}view/cust_$thing.html?$q", $key ]
}
my $cust_link = sub {
diff --git a/httemplate/search/elements/search-csv.html b/httemplate/search/elements/search-csv.html
new file mode 100644
index 0000000..cd4ea63
--- /dev/null
+++ b/httemplate/search/elements/search-csv.html
@@ -0,0 +1,48 @@
+% $csv->combine(@$header); #or die $csv->status;
+%
+<% $csv->string %>\
+%
+% foreach my $row ( @$rows ) {
+%
+% if ( $opt{'fields'} ) {
+%
+% my @line = ();
+%
+% foreach my $field ( @{$opt{'fields'}} ) {
+% if ( ref($field) eq 'CODE' ) {
+% push @line, map {
+% ref($_) eq 'ARRAY'
+% ? '(N/A)' #unimplemented
+% : $_;
+% }
+% &{$field}($row);
+% } else {
+% push @line, $row->$field();
+% }
+% }
+%
+% $csv->combine(@line); #or die $csv->status;
+%
+% } else {
+% $csv->combine(@$row); #or die $csv->status;
+% }
+%
+%
+<% $csv->string %>\
+%
+% }
+<%init>
+
+my %args = @_;
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my %opt = %{ $args{'opt'} };
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+http_header('Content-Type' => 'text/plain' );
+
+my $csv = new Text::CSV_XS { 'always_quote' => 1,
+ 'eol' => "\n", #"\015\012", #"\012"
+ };
+
+</%init>
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
new file mode 100644
index 0000000..297774d
--- /dev/null
+++ b/httemplate/search/elements/search-html.html
@@ -0,0 +1,454 @@
+%
+% if ( exists($opt{'redirect'}) && $opt{'redirect'}
+% && scalar(@$rows) == 1 && $total == 1
+% && $type ne 'html-print'
+% ) {
+% my $redirect = $opt{'redirect'};
+% $redirect = &{$redirect}($rows->[0], $cgi) if ref($redirect) eq 'CODE';
+% my( $url, $method ) = @$redirect;
+% redirect( $url. $rows->[0]->$method() );
+% } elsif ( exists($opt{'redirect_empty'}) && ! scalar(@$rows) && $total == 0
+% && $type ne 'html-print'
+% && $opt{'redirect_empty'}
+% && ( ref($opt{'redirect_empty'}) ne 'CODE'
+% || &{$opt{'redirect_empty'}}($cgi) )
+% ) {
+% my $redirect = $opt{'redirect_empty'};
+% $redirect = &{$redirect}($cgi) if ref($redirect) eq 'CODE';
+% redirect( $redirect );
+% } else {
+% if ( $opt{'name_singular'} ) {
+% $opt{'name'} = PL($opt{'name_singular'});
+% }
+% ( my $xlsname = $opt{'name'} ) =~ s/\W//g;
+% if ( $total == 1 ) {
+% if ( $opt{'name_singular'} ) {
+% $opt{'name'} = $opt{'name_singular'}
+% } else {
+% #$opt{'name'} =~ s/s$// if $total == 1;
+% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1;
+% }
+% }
+%
+% if ( $type eq 'html-print' ) {
+
+ <% include( '/elements/header-popup.html', $opt{'title'} ) %>
+
+% } elsif ( $type eq 'select' ) {
+
+ <% include( '/elements/header-popup.html', $opt{'title'} ) %>
+ <% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+ %>
+
+% } else {
+%
+% my @menubar = ();
+% if ( $opt{'menubar'} ) {
+% @menubar = @{ $opt{'menubar'} };
+% #} else {
+% # @menubar = ( 'Main menu' => $p );
+% }
+
+ <% include( '/elements/header.html', $opt{'title'},
+ include( '/elements/menubar.html', @menubar )
+ )
+ %>
+
+ <% defined($opt{'html_init'})
+ ? ( ref($opt{'html_init'})
+ ? &{$opt{'html_init'}}()
+ : $opt{'html_init'}
+ )
+ : ''
+ %>
+
+% }
+
+% unless ( $total ) {
+% unless ( $opt{'disable_nonefound'} ) {
+ No matching <% $opt{'name'} %> found.<BR>
+% }
+% }
+%
+% if ( $total || $opt{'disableable'} ) { #hmm... and there *are* ones to show??
+
+ <TABLE>
+ <TR>
+
+ <TD VALIGN="bottom">
+
+ <FORM>
+
+% if (! $opt{'disable_total'}) {
+ <% $total %> total <% $opt{'name'} %>
+% }
+
+% if ( $confmax && $total > $confmax
+% && ! $opt{'disable_maxselect'}
+% && $type ne 'html-print' )
+% {
+% $cgi->delete('maxrecords');
+% $cgi->param('_dummy', 1);
+
+ ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->self_url %>;maxrecords=' + this.options[this.selectedIndex].value;">
+
+% foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) {
+ <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION>
+% }
+
+ </SELECT> per page )
+
+% $cgi->param('maxrecords', $maxrecords);
+% }
+
+% if ( defined($opt{'html_posttotal'}) && $type ne 'html-print' ) {
+ <% ref($opt{'html_posttotal'})
+ ? &{$opt{'html_posttotal'}}()
+ : $opt{'html_posttotal'}
+ %>
+% }
+ <BR>
+
+% if ( $opt{'count_addl'} ) {
+% my $n=0;
+% foreach my $count ( @{$opt{'count_addl'}} ) {
+% my $data = $count_arrayref->[++$n];
+% if ( ref($count) ) {
+ <% &{ $count }( $data ) %>
+% } else {
+ <% sprintf( $count, $data ) %><BR>
+% }
+% }
+% }
+ </FORM>
+
+ </TD>
+
+% unless ( $opt{'disable_download'} || $type eq 'html-print' ) {
+
+ <TD ALIGN="right">
+
+ Download full results<BR>
+
+% $cgi->param('_type', "$xlsname.xls" );
+ as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR>
+
+% $cgi->param('_type', 'csv');
+ as <A HREF="<% $cgi->self_url %>">CSV file</A><BR>
+
+% $cgi->param('_type', 'html-print');
+ as <A HREF="<% $cgi->self_url %>">printable copy</A>
+
+ <% $opt{'extra_choices_callback'}
+ ? &{$opt{'extra_choices_callback'}}($cgi->query_string)
+ : ''
+ %>
+
+ </TD>
+% $cgi->param('_type', "html" );
+% }
+
+ </TR>
+ <TR>
+ <TD COLSPAN=2>
+
+% my $pager = '';
+% unless ( $type eq 'html_print' ) {
+
+ <% $pager = include( '/elements/pager.html',
+ 'offset' => $offset,
+ 'num_rows' => scalar(@$rows),
+ 'total' => $total,
+ 'maxrecords' => $maxrecords,
+ )
+ %>
+
+ <% defined($opt{'html_form'})
+ ? ( ref($opt{'html_form'})
+ ? &{$opt{'html_form'}}()
+ : $opt{'html_form'}
+ )
+ : ''
+ %>
+
+% }
+
+ <% include('/elements/table-grid.html') %>
+
+ <TR>
+% my $h2 = 0;
+% foreach my $header ( @{ $opt{header} } ) {
+% my $label = ref($header) ? $header->{label} : $header;
+% my $rowspan = 1;
+% my $style = '';
+% if ( $opt{header2} ) {
+% if ( !length($opt{header2}->[$h2]) ) {
+% $rowspan = 2;
+% splice @{ $opt{header2} }, $h2, 1;
+% } else {
+% $h2++;
+% $style = 'STYLE="border-bottom: none"'
+% }
+% }
+ <TH CLASS = "grid"
+ BGCOLOR = "#cccccc"
+ ROWSPAN = "<% $rowspan %>"
+ <% $style %>
+
+ >
+ <% $label %>
+ </TH>
+% }
+ </TR>
+
+% if ( $opt{header2} ) {
+ <TR>
+% foreach my $header ( @{ $opt{header2} } ) {
+% my $label = ref($header) ? $header->{label} : $header;
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE="-1"><% $label %></FONT>
+ </TH>
+% }
+ </TR>
+% }
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% foreach my $row ( @$rows ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+% if ( $opt{'fields'} ) {
+%
+% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+% my $onclicks = $opt{'link_onclicks'} ? [ @{$opt{'link_onclicks'}} ] : [];
+% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : [];
+% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
+% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : [];
+% my $cstyles = $opt{'cell_style'} ? [ @{$opt{'cell_style'}} ] : [];
+%
+% foreach my $field (
+%
+% map {
+% if ( ref($_) eq 'ARRAY' ) {
+%
+% my $tableref = $_;
+%
+% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 WIDTH="100%">'.
+%
+% join('', map {
+%
+% my $rowref = $_;
+%
+% '<tr>'.
+%
+% join('', map {
+%
+% my $e = $_;
+%
+% '<TD '.
+% join(' ', map {
+% uc($_).'="'. $e->{$_}. '"';
+% }
+% grep exists($e->{$_}),
+% qw( align bgcolor colspan rowspan
+% style valign width )
+% ).
+% '>'.
+%
+% ( $e->{'link'}
+% ? '<A HREF="'. $e->{'link'}. '">'
+% : ''
+% ).
+% ( $e->{'size'}
+% ? '<FONT SIZE="'.uc($e->{'size'}).'">'
+% : ''
+% ).
+% ( $e->{'data_style'}
+% ? '<'. uc($e->{'data_style'}). '>'
+% : ''
+% ).
+% $e->{'data'}.
+% ( $e->{'data_style'}
+% ? '</'. uc($e->{'data_style'}). '>'
+% : ''
+% ).
+% ( $e->{'size'} ? '</FONT>' : '' ).
+% ( $e->{'link'} ? '</A>' : '' ).
+% '</td>';
+%
+% } @$rowref ).
+%
+% '</tr>';
+% } @$tableref ).
+%
+% '</table>';
+%
+% } else {
+% $_;
+% }
+% }
+%
+% map {
+% if ( ref($_) eq 'CODE' ) {
+% &{$_}($row);
+% } else {
+% $row->$_();
+% }
+% }
+% @{$opt{'fields'}}
+%
+% ) {
+%
+% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
+%
+% my $align = $aligns ? shift @$aligns : '';
+% $align = " ALIGN=$align" if $align;
+%
+% my $a = '';
+% if ( $links ) {
+% my $link = shift @$links;
+% my $onclick = shift @$onclicks;
+%
+% if ( ! $opt{'agent_virt'}
+% || ( $null_link && ! $row->agentnum )
+% || grep { $row->agentnum == $_ }
+% @link_agentnums
+% ) {
+%
+% $link = &{$link}($row)
+% if ref($link) eq 'CODE';
+%
+% $onclick = &{$onclick}($row)
+% if ref($onclick) eq 'CODE';
+% $onclick = qq( onClick="$onclick") if $onclick;
+%
+% if ( $link ) {
+% my( $url, $method ) = @{$link};
+% if ( ref($method) eq 'CODE' ) {
+% $a = $url. &{$method}($row);
+% } else {
+% $a = $url. $row->$method();
+% }
+% $a = qq(<A HREF="$a"$onclick>);
+% }
+%
+% }
+%
+% }
+%
+% my $font = '';
+% my $color = shift @$colors;
+% $color = &{$color}($row) if ref($color) eq 'CODE';
+% my $size = shift @$sizes;
+% $size = &{$size}($row) if ref($size) eq 'CODE';
+% if ( $color || $size ) {
+% $font = '<FONT '.
+% ( $color ? "COLOR=#$color " : '' ).
+% ( $size ? qq(SIZE="$size" ) : '' ).
+% '>';
+% }
+%
+% my($s, $es) = ( '', '' );
+% my $style = shift @$styles;
+% $style = &{$style}($row) if ref($style) eq 'CODE';
+% if ( $style ) {
+% $s = join( '', map "<$_>", split('', $style) );
+% $es = join( '', map "</$_>", split('', $style) );
+% }
+%
+% my $cstyle = shift @$cstyles;
+% $cstyle = &{$cstyle}($row) if ref($cstyle) eq 'CODE';
+% $cstyle = qq(STYLE="$cstyle")
+% if $cstyle;
+
+ <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>" <% $align %> <% $cstyle %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD>
+
+% }
+%
+% } else {
+%
+% foreach ( @$row ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD>
+% }
+%
+% }
+
+ </TR>
+
+% }
+
+% if ( $opt{'footer'} ) {
+
+ <TR>
+
+% foreach my $footer ( @{ $opt{'footer'} } ) {
+ <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TD>
+% }
+
+ </TR>
+% }
+
+ </TABLE>
+
+ <% $pager %>
+
+ </TD>
+ </TR>
+ </TABLE>
+% }
+
+% if ( $type eq 'html-print' ) {
+
+ </BODY></HTML>
+
+% } else {
+
+ <% defined($opt{'html_foot'})
+ ? ( ref($opt{'html_foot'})
+ ? &{$opt{'html_foot'}}()
+ : $opt{'html_foot'}
+ )
+ : ''
+ %>
+
+ <% include( '/elements/footer.html' ) %>
+
+% }
+
+% }
+<%init>
+
+my %args = @_;
+my $type = $args{'type'};
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my @link_agentnums = @{ $args{'link_agentnums'} };
+my $null_link = $args{'null_link'};
+my $confmax = $args{'confmax'};
+my $maxrecords = $args{'maxrecords'};
+my $offset = $args{'offset'};
+my %opt = %{ $args{'opt'} };
+
+my $count_sth = dbh->prepare($opt{'count_query'})
+ or die "Error preparing $opt{'count_query'}: ". dbh->errstr;
+$count_sth->execute
+ or die "Error executing $opt{'count_query'}: ". $count_sth->errstr;
+my $count_arrayref = $count_sth->fetchrow_arrayref;
+my $total = $count_arrayref->[0];
+
+</%init>
diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html
new file mode 100644
index 0000000..8a05e47
--- /dev/null
+++ b/httemplate/search/elements/search-xls.html
@@ -0,0 +1,83 @@
+<% $data %>
+<%init>
+
+my %args = @_;
+my $type = $args{'type'};
+my $header = $args{'header'};
+my $rows = $args{'rows'};
+my %opt = %{ $args{'opt'} };
+
+#http_header('Content-Type' => 'application/excel' ); #eww
+#http_header('Content-Type' => 'application/msexcel' ); #alas
+#http_header('Content-Type' => 'application/x-msexcel' ); #?
+
+#http://support.microsoft.com/kb/199841
+http_header('Content-Type' => 'application/vnd.ms-excel' );
+
+#http://support.microsoft.com/kb/812935
+#http://support.microsoft.com/kb/323308
+$HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0';
+
+my $data = '';
+my $XLS = new IO::Scalar \$data;
+my $workbook = Spreadsheet::WriteExcel->new($XLS)
+ or die "Error opening .xls file: $!";
+
+my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
+
+$worksheet->protect();
+
+my($r,$c) = (0,0);
+
+my $header_format = $workbook->add_format(
+ bold => 1,
+ locked => 1,
+ bg_color => 55, #22,
+ bottom => 3,
+);
+
+$worksheet->write($r, $c++, $_, $header_format ) foreach @$header;
+
+foreach my $row ( @$rows ) {
+ $r++;
+ $c = 0;
+
+ if ( $opt{'fields'} ) {
+
+ #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+ #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+ #could also translate color, size, style into xls equivalents?
+ my $formats = $opt{'xls_format'} ? [ @{$opt{'xls_format'}} ] : [];
+
+ foreach my $field ( @{$opt{'fields'}} ) {
+
+ my $format = shift @$formats;
+ $format = &{$format}($row) if ref($format) eq 'CODE';
+ $format ||= {};
+ my $xls_format = $workbook->add_format(locked=>0, %$format);
+
+ if ( ref($field) eq 'CODE' ) {
+ foreach my $value ( &{$field}($row) ) {
+ if ( ref($value) eq 'ARRAY' ) {
+ $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
+ } else {
+ $worksheet->write($r, $c++, $value, $xls_format );
+ }
+ }
+ } else {
+ $worksheet->write($r, $c++, $row->$field(), $xls_format );
+ }
+ }
+
+ } else {
+ my $xls_format = $workbook->add_format(locked=>0);
+ $worksheet->write($r, $c++, $_, $xls_format ) foreach @$row;
+ }
+
+}
+
+$workbook->close();# or die "Error creating .xls file: $!";
+
+http_header('Content-Length' => length($data) );
+
+</%init>
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index 8835f8c..4bfe8b0 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -16,7 +16,8 @@ Example:
# (deprecated, will be singularlized
# simplisticly)
- #literal SQL query string (deprecated?) or qsearch hashref
+ #literal SQL query string (deprecated?) or qsearch hashref or arrayref
+ #of qsearch hashrefs for a union of qsearches
'query' => {
'table' => 'tablename',
#everything else is optional...
@@ -148,570 +149,42 @@ Example:
'align' => 'lrc.',
#listrefs of ( scalars or coderefs )
- #currently only HTML, maybe eventually Excel too
+ # currently only HTML, maybe eventually Excel too
'color' => [],
'size' => [],
'style' => [], #<B> or <I>, etc.
'cell_style' => [], #STYLE= attribute of TR, very HTML-specific...
+
+ # Excel-specific listref of ( hashrefs or coderefs )
+ # each hashref: http://search.cpan.org/dist/Spreadsheet-WriteExcel/lib/Spreadsheet/WriteExcel.pm#Format_methods_and_Format_properties
+ 'xls_format' => => [],
);
</%doc>
% if ( $type eq 'csv' ) {
%
-% #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
-% http_header('Content-Type' => 'text/plain' );
-%
-% my $csv = new Text::CSV_XS { 'always_quote' => 1,
-% 'eol' => "\n", #"\015\012", #"\012"
-% };
-%
-% $csv->combine(@$header); #or die $csv->status;
-%
-<% $csv->string %>
-%
-%
-% foreach my $row ( @$rows ) {
-%
-% if ( $opt{'fields'} ) {
-%
-% my @line = ();
-%
-% foreach my $field ( @{$opt{'fields'}} ) {
-% if ( ref($field) eq 'CODE' ) {
-% push @line, map {
-% ref($_) eq 'ARRAY'
-% ? '(N/A)' #unimplemented
-% : $_;
-% }
-% &{$field}($row);
-% } else {
-% push @line, $row->$field();
-% }
-% }
-%
-% $csv->combine(@line); #or die $csv->status;
-%
-% } else {
-% $csv->combine(@$row); #or die $csv->status;
-% }
-%
-%
-<% $csv->string %>
-%
-%
-% }
+<% include('search-csv.html', header=>$header, rows=>$rows, opt=>\%opt ) %>
%
% #} elsif ( $type eq 'excel' ) {
% } elsif ( $type =~ /\.xls$/ ) {
%
-% #http_header('Content-Type' => 'application/excel' ); #eww
-% #http_header('Content-Type' => 'application/msexcel' ); #alas
-% #http_header('Content-Type' => 'application/x-msexcel' ); #?
-%
-% #http://support.microsoft.com/kb/199841
-% http_header('Content-Type' => 'application/vnd.ms-excel' );
-%
-% #http://support.microsoft.com/kb/812935
-% #http://support.microsoft.com/kb/323308
-% $HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0';
-%
-% my $data = '';
-% my $XLS = new IO::Scalar \$data;
-% my $workbook = Spreadsheet::WriteExcel->new($XLS)
-% or die "Error opening .xls file: $!";
-%
-% my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
-%
-% my($r,$c) = (0,0);
-%
-% $worksheet->write($r, $c++, $_) foreach @$header;
-%
-% foreach my $row ( @$rows ) {
-% $r++;
-% $c = 0;
-%
-% if ( $opt{'fields'} ) {
-%
-% #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
-% #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
-%
-% foreach my $field ( @{$opt{'fields'}} ) {
-% #my $align = $aligns ? shift @$aligns : '';
-% #$align = " ALIGN=$align" if $align;
-% #my $a = '';
-% #if ( $links ) {
-% # my $link = shift @$links;
-% # $link = &{$link}($row) if ref($link) eq 'CODE';
-% # if ( $link ) {
-% # my( $url, $method ) = @{$link};
-% # if ( ref($method) eq 'CODE' ) {
-% # $a = $url. &{$method}($row);
-% # } else {
-% # $a = $url. $row->$method();
-% # }
-% # $a = qq(<A HREF="$a">);
-% # }
-% #}
-% if ( ref($field) eq 'CODE' ) {
-% foreach my $value ( &{$field}($row) ) {
-% if ( ref($value) eq 'ARRAY' ) {
-% $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
-% } else {
-% $worksheet->write($r, $c++, $value );
-% }
-% }
-% } else {
-% $worksheet->write($r, $c++, $row->$field() );
-% }
-% }
-%
-% } else {
-% $worksheet->write($r, $c++, $_) foreach @$row;
-% }
-%
-% }
-%
-% $workbook->close();# or die "Error creating .xls file: $!";
-%
-% http_header('Content-Length' => length($data) );
-%
-<% $data %>
-%
+<% include('search-xls.html', header=>$header, rows=>$rows, opt=>\%opt ) %>
%
% } else { # regular HTML
%
-% if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1
-% && $type ne 'html-print'
-% ) {
-% my $redirect = $opt{'redirect'};
-% $redirect = &{$redirect}($rows->[0], $cgi) if ref($redirect) eq 'CODE';
-% my( $url, $method ) = @$redirect;
-% redirect( $url. $rows->[0]->$method() );
-% } elsif ( exists($opt{'redirect_empty'}) && ! scalar(@$rows) && $total == 0
-% && $type ne 'html-print'
-% && $opt{'redirect_empty'}
-% && ( ref($opt{'redirect_empty'}) ne 'CODE'
-% || &{$opt{'redirect_empty'}}($cgi) )
-% ) {
-% my $redirect = $opt{'redirect_empty'};
-% $redirect = &{$redirect}($cgi) if ref($redirect) eq 'CODE';
-% redirect( $redirect );
-% } else {
-% if ( $opt{'name_singular'} ) {
-% $opt{'name'} = PL($opt{'name_singular'});
-% }
-% ( my $xlsname = $opt{'name'} ) =~ s/\W//g;
-% if ( $total == 1 ) {
-% if ( $opt{'name_singular'} ) {
-% $opt{'name'} = $opt{'name_singular'}
-% } else {
-% #$opt{'name'} =~ s/s$// if $total == 1;
-% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1;
-% }
-% }
-%
-% if ( $type eq 'html-print' ) {
-
- <% include( '/elements/header-popup.html', $opt{'title'} ) %>
-
-% } elsif ( $type eq 'select' ) {
-
- <% include( '/elements/header-popup.html', $opt{'title'} ) %>
- <% defined($opt{'html_init'})
- ? ( ref($opt{'html_init'})
- ? &{$opt{'html_init'}}()
- : $opt{'html_init'}
- )
- : ''
- %>
-
-% } else {
-%
-% my @menubar = ();
-% if ( $opt{'menubar'} ) {
-% @menubar = @{ $opt{'menubar'} };
-% #} else {
-% # @menubar = ( 'Main menu' => $p );
-% }
-
- <% include( '/elements/header.html', $opt{'title'},
- include( '/elements/menubar.html', @menubar )
- )
- %>
-
- <% defined($opt{'html_init'})
- ? ( ref($opt{'html_init'})
- ? &{$opt{'html_init'}}()
- : $opt{'html_init'}
- )
- : ''
- %>
-
-% }
-
-% unless ( $total ) {
-% unless ( $opt{'disable_nonefound'} ) {
- No matching <% $opt{'name'} %> found.<BR>
-% }
-% }
-%
-% if ( $total || $opt{'disableable'} ) { #hmm... and there *are* ones to show??
-
- <TABLE>
- <TR>
-
- <TD VALIGN="bottom">
-
- <FORM>
-
-% if (! $opt{'disable_total'}) {
- <% $total %> total <% $opt{'name'} %>
-% }
-
-% if ( $confmax && $total > $confmax
-% && ! $opt{'disable_maxselect'}
-% && $type ne 'html-print' )
-% {
-% $cgi->delete('maxrecords');
-% $cgi->param('_dummy', 1);
-
- ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->self_url %>;maxrecords=' + this.options[this.selectedIndex].value;">
-
-% foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) {
- <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION>
-% }
-
- </SELECT> per page )
-
-% $cgi->param('maxrecords', $maxrecords);
-% }
-
-% if ( defined($opt{'html_posttotal'}) && $type ne 'html-print' ) {
- <% ref($opt{'html_posttotal'})
- ? &{$opt{'html_posttotal'}}()
- : $opt{'html_posttotal'}
- %>
-% }
- <BR>
-
-% if ( $opt{'count_addl'} ) {
-% my $n=0;
-% foreach my $count ( @{$opt{'count_addl'}} ) {
-% my $data = $count_arrayref->[++$n];
-% if ( ref($count) ) {
- <% &{ $count }( $data ) %>
-% } else {
- <% sprintf( $count, $data ) %><BR>
-% }
-% }
-% }
- </FORM>
-
- </TD>
-
-% unless ( $opt{'disable_download'} || $type eq 'html-print' ) {
-
- <TD ALIGN="right">
-
- Download full results<BR>
-
-% $cgi->param('_type', "$xlsname.xls" );
- as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR>
-
-% $cgi->param('_type', 'csv');
- as <A HREF="<% $cgi->self_url %>">CSV file</A><BR>
-
-% $cgi->param('_type', 'html-print');
- as <A HREF="<% $cgi->self_url %>">printable copy</A>
-
- <% $opt{'extra_choices_callback'}
- ? &{$opt{'extra_choices_callback'}}($cgi->query_string)
- : ''
- %>
-
- </TD>
-% $cgi->param('_type', "html" );
-% }
-
- </TR>
- <TR>
- <TD COLSPAN=2>
-
-% my $pager = '';
-% unless ( $type eq 'html_print' ) {
-
- <% $pager = include( '/elements/pager.html',
- 'offset' => $offset,
- 'num_rows' => scalar(@$rows),
- 'total' => $total,
- 'maxrecords' => $maxrecords,
- )
- %>
-
- <% defined($opt{'html_form'})
- ? ( ref($opt{'html_form'})
- ? &{$opt{'html_form'}}()
- : $opt{'html_form'}
- )
- : ''
- %>
-
-% }
-
- <% include('/elements/table-grid.html') %>
-
- <TR>
-% my $h2 = 0;
-% foreach my $header ( @{ $opt{header} } ) {
-% my $label = ref($header) ? $header->{label} : $header;
-% my $rowspan = 1;
-% my $style = '';
-% if ( $opt{header2} ) {
-% if ( !length($opt{header2}->[$h2]) ) {
-% $rowspan = 2;
-% splice @{ $opt{header2} }, $h2, 1;
-% } else {
-% $h2++;
-% $style = 'STYLE="border-bottom: none"'
-% }
-% }
- <TH CLASS = "grid"
- BGCOLOR = "#cccccc"
- ROWSPAN = "<% $rowspan %>"
- <% $style %>
-
- >
- <% $label %>
- </TH>
-% }
- </TR>
-
-% if ( $opt{header2} ) {
- <TR>
-% foreach my $header ( @{ $opt{header2} } ) {
-% my $label = ref($header) ? $header->{label} : $header;
- <TH CLASS="grid" BGCOLOR="#cccccc">
- <FONT SIZE="-1"><% $label %></FONT>
- </TH>
-% }
- </TR>
-% }
-
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor;
-%
-% foreach my $row ( @$rows ) {
-%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-
- <TR>
-
-% if ( $opt{'fields'} ) {
-%
-% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
-% my $onclicks = $opt{'link_onclicks'} ? [ @{$opt{'link_onclicks'}} ] : [];
-% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
-% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : [];
-% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
-% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : [];
-% my $cstyles = $opt{'cell_style'} ? [ @{$opt{'cell_style'}} ] : [];
-%
-% foreach my $field (
-%
-% map {
-% if ( ref($_) eq 'ARRAY' ) {
-%
-% my $tableref = $_;
-%
-% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 WIDTH="100%">'.
-%
-% join('', map {
-%
-% my $rowref = $_;
-%
-% '<tr>'.
-%
-% join('', map {
-%
-% my $e = $_;
-%
-% '<TD '.
-% join(' ', map {
-% uc($_).'="'. $e->{$_}. '"';
-% }
-% grep exists($e->{$_}),
-% qw( align bgcolor colspan rowspan
-% style valign width )
-% ).
-% '>'.
-%
-% ( $e->{'link'}
-% ? '<A HREF="'. $e->{'link'}. '">'
-% : ''
-% ).
-% ( $e->{'size'}
-% ? '<FONT SIZE="'.uc($e->{'size'}).'">'
-% : ''
-% ).
-% ( $e->{'data_style'}
-% ? '<'. uc($e->{'data_style'}). '>'
-% : ''
-% ).
-% $e->{'data'}.
-% ( $e->{'data_style'}
-% ? '</'. uc($e->{'data_style'}). '>'
-% : ''
-% ).
-% ( $e->{'size'} ? '</FONT>' : '' ).
-% ( $e->{'link'} ? '</A>' : '' ).
-% '</td>';
-%
-% } @$rowref ).
-%
-% '</tr>';
-% } @$tableref ).
-%
-% '</table>';
-%
-% } else {
-% $_;
-% }
-% }
-%
-% map {
-% if ( ref($_) eq 'CODE' ) {
-% &{$_}($row);
-% } else {
-% $row->$_();
-% }
-% }
-% @{$opt{'fields'}}
-%
-% ) {
-%
-% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
-%
-% my $align = $aligns ? shift @$aligns : '';
-% $align = " ALIGN=$align" if $align;
-%
-% my $a = '';
-% if ( $links ) {
-% my $link = shift @$links;
-% my $onclick = shift @$onclicks;
-%
-% if ( ! $opt{'agent_virt'}
-% || ( $null_link && ! $row->agentnum )
-% || grep { $row->agentnum == $_ }
-% @link_agentnums
-% ) {
-%
-% $link = &{$link}($row)
-% if ref($link) eq 'CODE';
-%
-% $onclick = &{$onclick}($row)
-% if ref($onclick) eq 'CODE';
-% $onclick = qq( onClick="$onclick") if $onclick;
-%
-% if ( $link ) {
-% my( $url, $method ) = @{$link};
-% if ( ref($method) eq 'CODE' ) {
-% $a = $url. &{$method}($row);
-% } else {
-% $a = $url. $row->$method();
-% }
-% $a = qq(<A HREF="$a"$onclick>);
-% }
-%
-% }
-%
-% }
-%
-% my $font = '';
-% my $color = shift @$colors;
-% $color = &{$color}($row) if ref($color) eq 'CODE';
-% my $size = shift @$sizes;
-% $size = &{$size}($row) if ref($size) eq 'CODE';
-% if ( $color || $size ) {
-% $font = '<FONT '.
-% ( $color ? "COLOR=#$color " : '' ).
-% ( $size ? qq(SIZE="$size" ) : '' ).
-% '>';
-% }
-%
-% my($s, $es) = ( '', '' );
-% my $style = shift @$styles;
-% $style = &{$style}($row) if ref($style) eq 'CODE';
-% if ( $style ) {
-% $s = join( '', map "<$_>", split('', $style) );
-% $es = join( '', map "</$_>", split('', $style) );
-% }
-%
-% my $cstyle = shift @$cstyles;
-% $cstyle = &{$cstyle}($row) if ref($cstyle) eq 'CODE';
-% $cstyle = qq(STYLE="$cstyle")
-% if $cstyle;
-
- <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>" <% $align %> <% $cstyle %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD>
-
-% }
-%
-% } else {
-%
-% foreach ( @$row ) {
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD>
-% }
-%
-% }
-
- </TR>
-
-% }
-
-% if ( $opt{'footer'} ) {
-
- <TR>
-
-% foreach my $footer ( @{ $opt{'footer'} } ) {
- <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TD>
-% }
-
- </TR>
-% }
-
- </TABLE>
-
- <% $pager %>
-
- </TD>
- </TR>
- </TABLE>
-% }
-
-% if ( $type eq 'html-print' ) {
-
- </BODY></HTML>
-
-% } else {
-
- <% defined($opt{'html_foot'})
- ? ( ref($opt{'html_foot'})
- ? &{$opt{'html_foot'}}()
- : $opt{'html_foot'}
- )
- : ''
- %>
-
- <% include( '/elements/footer.html' ) %>
-
-% }
-
-% }
+<% include('search-html.html',
+ type => $type,
+ header => $header,
+ rows => $rows,
+ link_agentnums => \@link_agentnums,
+ null_link => $null_link,
+ confmax => $confmax,
+ maxrecords => $maxrecords,
+ offset => $offset,
+ opt => \%opt
+ )
+%>
%
% }
<%init>
@@ -759,7 +232,7 @@ if ( $opt{'agent_virt'} ) {
#false laziness w/statuspos above
my $pos = $opt{'agent_pos'};
- foreach my $att (qw( align style color size )) {
+ foreach my $att (qw( align color size style cell_style xls_format )) {
$opt{$att} ||= [ map '', @{ $opt{'fields'} } ];
}
@@ -833,7 +306,7 @@ my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|select|html(-print)?)$/
? $1 : 'html';
my $limit = '';
-my($confmax, $maxrecords, $total, $offset, $count_arrayref);
+my($confmax, $maxrecords, $offset );
unless ( $type =~ /^(csv|\w*\.xls)$/ ) {
@@ -867,13 +340,6 @@ unless ( $type =~ /^(csv|\w*\.xls)$/ ) {
}
- my $count_sth = dbh->prepare($opt{'count_query'})
- or die "Error preparing $opt{'count_query'}: ". dbh->errstr;
- $count_sth->execute
- or die "Error executing $opt{'count_query'}: ". $count_sth->errstr;
- $count_arrayref = $count_sth->fetchrow_arrayref;
- $total = $count_arrayref->[0];
-
}
# run the query
@@ -882,6 +348,15 @@ my $header = [ map { ref($_) ? $_->{'label'} : $_ } @{$opt{header}} ];
my $rows;
if ( ref($opt{query}) ) {
+ my @query;
+ if (ref($opt{query}) eq 'HASH') {
+ @query = ( $opt{query} );
+ } elsif (ref($opt{query}) eq 'ARRAY') {
+ @query = @{ $opt{query} };
+ } else {
+ die "invalid query reference";
+ }
+
if ( $opt{disableable} && ! $cgi->param('showdisabled') ) {
#%search = ( 'disabled' => '' );
$opt{'query'}->{'hashref'}->{'disabled'} = '';
@@ -889,14 +364,15 @@ if ( ref($opt{query}) ) {
}
#eval "use FS::$opt{'query'};";
- $rows = [ qsearch({
- 'select' => $opt{'query'}->{'select'},
- 'table' => $opt{'query'}->{'table'},
- 'addl_from' => (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : ''),
- 'hashref' => $opt{'query'}->{'hashref'} || {},
- 'extra_sql' => $opt{'query'}->{'extra_sql'},
- 'order_by' => $opt{'query'}->{'order_by'}. " $limit",
- }) ];
+ my @param = qw( select table addl_from hashref extra_sql order_by );
+ $rows = [ qsearch( [ map { my $query = $_;
+ ({ map { $_ => $query->{$_} } @param });
+ }
+ @query
+ ],
+ 'order_by' => $opt{order_by}. " ". $limit,
+ )
+ ];
} else {
my $sth = dbh->prepare("$opt{'query'} $limit")
or die "Error preparing $opt{'query'}: ". dbh->errstr;
diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html
index f65b00d..f7d6d20 100644
--- a/httemplate/search/reg_code.html
+++ b/httemplate/search/reg_code.html
@@ -13,7 +13,7 @@
sub {
map {
qq!<A HREF="${p}edit/part_pkg.cgi?!. $_->pkgpart. '">'.
- $_->pkg. ' - '. $_->comment.
+ $_->pkg_comment(nopkgpart => 1).
'</A><BR>'
} $_[0]->part_pkg
},
diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html
new file mode 100755
index 0000000..7b85c13
--- /dev/null
+++ b/httemplate/search/report_477.html
@@ -0,0 +1,64 @@
+<% include('/elements/header.html', 'FCC Form 477 Report' ) %>
+
+<FORM ACTION="477.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="active">
+
+ <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html',
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'disable_empty' => 0,
+ )
+ %>
+
+ <% include( '/elements/tr-select-pkg_class.html',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(empty class)',
+ )
+ %>
+
+% if ( scalar( qsearch( 'part_pkg_report_option', { 'disabled' => '' } ) ) ) {
+% # the m2 javascript magic in edit/elements/edit.html would be better here
+
+ <% include( '/elements/tr-select-table.html',
+ 'label' => 'Column report classes',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'column_option',
+ 'multiple' => 'multiple',
+ )
+ %>
+
+ <% include( '/elements/tr-select-table.html',
+ 'label' => 'Row report classes',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'row_option',
+ 'multiple' => 'multiple',
+ )
+ %>
+
+% }
+
+ </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+
+</%init>
diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html
index 2851631..c685198 100644
--- a/httemplate/search/report_cdr.html
+++ b/httemplate/search/report_cdr.html
@@ -3,6 +3,13 @@
<FORM ACTION="cdr.html" METHOD="GET">
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
<TR>
<TD ALIGN="right">Status: </TD>
<TD>
@@ -14,6 +21,23 @@
</TD>
</TR>
+% #if ( ) { # disable for everyone not using termination billing...
+% foreach my $termpart ( 1..1 ) { #qsearch('part_termination
+
+ <TR>
+ <TD ALIGN="right">Termination Status: </TD>
+ <TD>
+ <SELECT NAME="termpart<%$termpart%>status">
+ <OPTION VALUE="">(all)
+ <OPTION VALUE="NULL">unprocessed
+ <OPTION VALUE="done">processed
+ </SELECT>
+ </TD>
+ </TR>
+
+% }
+% #}
+
<% include ( '/elements/tr-input-beginning_ending.html' ) %>
<TR>
@@ -30,6 +54,21 @@
</TD>
</TR>
+ <TR>
+ <TD ALIGN="right">Destination Context: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="dcontext">
+ </TD>
+ </TR>
+
+
+ <TR>
+ <TD ALIGN="right">Charged Party #: </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="charged_party">
+ </TD>
+ </TR>
+
<% include( '/elements/tr-input-lessthan_greaterthan.html',
'label' => 'Duration (sec)',
'field' => 'duration',
@@ -44,15 +83,66 @@
<% include( '/elements/tr-select-cdrbatch.html' ) %>
+ <TR>
+ <TD ALIGN="right">Acct ID (one per-line):</TD>
+ <TD><TEXTAREA NAME="acctid"></TEXTAREA></TD>
+ </TR>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TH>
+ </TR>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
+ </TR>
+
+ <INPUT TYPE="hidden" NAME="show" VALUE="1">
+
+ <TR>
+ <TD COLSPAN=2>
+ <% include('/elements/checkboxes.html',
+ 'names_list' => $names_list,
+ 'element_name_prefix' => 'show_',
+ 'checked_callback' => sub { $show_default{$_[1]} },
+ # my($cgi, $name) = @_;
+ )
+ %>
+ </TD>
+ </TR>
+
</TABLE>
<BR>
<INPUT TYPE="submit" VALUE="Search Call Detail Records">
+</FORM>
+
<% include('/elements/footer.html') %>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+my @fields = fields('cdr');
+my $labels = FS::cdr->table_info->{'fields'};
+
+#XXX config
+my @show_default = qw(
+ calldate clid src dst dcontext charged_party
+ startdate answerdate enddate duration billsec
+ disposition amaflags accountcode userfield
+ rated_price upstream_price carrierid
+ svcnum freesidestatus freesiderewritestatus cdrbatch
+);
+my %show_default = map { $_=>1 } @show_default;
+
+my $names_list = [ map {
+ [ $_ => {
+ 'label' => 'Show '. ( $labels->{$_} || $_ )
+ }
+ ]
+ }
+ @fields
+ ];
+
</%init>
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index b0c5fde..f139d4b 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -56,6 +56,15 @@
<TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD>
</TR>
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Without census tract</TD>
+ <TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD>
+ </TR>
+
+% }
+
<TR>
<TH BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TH>
</TR>
@@ -84,6 +93,8 @@ die "access denied"
$FS::CurrentUser::CurrentUser->access_right('List packages')
);;
+my $conf = new FS::Conf;
+
</%init>
<%once>
diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html
index 0627131..dd2358a 100644
--- a/httemplate/search/report_cust_pay.html
+++ b/httemplate/search/report_cust_pay.html
@@ -1,9 +1,15 @@
-<% include('/elements/header.html', 'Payment report' ) %>
+<% include('/elements/header.html', $title ) %>
-<FORM ACTION="cust_pay.cgi" METHOD="GET">
+<FORM ACTION="<% $void ? 'cust_pay_void.html' : 'cust_pay.cgi' %>" METHOD="GET">
<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
-<TABLE>
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
<TR>
<TD ALIGN="right">Payments of type: </TD>
@@ -55,7 +61,32 @@
)
%>
- <% include( '/elements/tr-input-beginning_ending.html' ) %>
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Payment</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+
+% if ( $void ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center">Voided</TD>
+ <TD>
+ <TABLE>
+ <% include( '/elements/tr-input-beginning_ending.html',
+ prefix => 'void',
+ layout => 'horiz',
+ )
+ %>
+ </TABLE>
+ </TD>
+ </TR>
+% }
<% include( '/elements/tr-input-lessthan_greaterthan.html',
'label' => 'Amount',
@@ -76,4 +107,8 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $void = $cgi->param('void') ? 1 : 0;
+
+my $title = $void ? 'Voided payment report' : 'Payment report';
+
</%init>
diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html
index aef1c24..66dd7d1 100755
--- a/httemplate/search/report_cust_pkg.html
+++ b/httemplate/search/report_cust_pkg.html
@@ -72,6 +72,20 @@
)
%>
+% if ( scalar( qsearch( 'part_pkg_report_option', { 'disabled' => '' } ) ) ) {
+
+ <% include( '/elements/tr-select-table.html',
+ 'label' => 'Report classes',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { 'disabled' => '' },
+ 'element_name' => 'report_option',
+ 'multiple' => 'multiple',
+ )
+ %>
+
+% }
+
% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
<TR>
@@ -89,6 +103,34 @@
% }
+ <SCRIPT TYPE="text/javascript">
+
+ function custom_changed(what) {
+
+ if ( what.checked ) {
+
+ what.form.pkgpart.disabled = true;
+ what.form.pkgpart.style.backgroundColor = '#dddddd';
+
+ } else {
+
+ what.form.pkgpart.disabled = false;
+ what.form.pkgpart.style.backgroundColor = '#ffffff';
+
+ }
+
+ }
+
+ </SCRIPT>
+
+ <% include( '/elements/tr-checkbox.html',
+ 'label' => 'Custom packages',
+ 'field' => 'custom',
+ 'value' => 1,
+ 'onchange' => 'custom_changed(this);',
+ )
+ %>
+
<% include( '/elements/tr-selectmultiple-part_pkg.html' ) %>
<TR>
@@ -129,6 +171,7 @@ my %label = (
#false laziness w/cust_pkg.cgi
my %disable = (
'all' => {},
+ 'not yet billed' => { 'setup'=>1, 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, },
'active' => { 'susp'=>1, 'cancel'=>1 },
'suspended' => { 'cancel' => 1 },
diff --git a/httemplate/search/report_newtax.cgi b/httemplate/search/report_newtax.cgi
index 586fddd..0fb5483 100755
--- a/httemplate/search/report_newtax.cgi
+++ b/httemplate/search/report_newtax.cgi
@@ -15,6 +15,7 @@
<TR>
<TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
</TR>
% my $bgcolor1 = '#eeeeee';
@@ -37,9 +38,11 @@
<TR>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $tax->{'label'} %></TD>
+ <% $tax->{base} ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
<A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax->{'tax'} ) %></A>
</TD>
+ <% !($tax->{base}) ? qq!<TD CLASS="grid" BGCOLOR="$bgcolor"></TD>! : '' %>
</TR>
% }
@@ -61,10 +64,11 @@ my $join_cust = "
JOIN cust_bill USING ( invnum )
LEFT JOIN cust_main USING ( custnum )
";
-my $from_join_cust = "
- FROM cust_bill_pkg
- $join_cust
-";
+
+my $join_loc = "LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum )";
+my $join_tax_loc = "LEFT JOIN tax_rate_location USING ( taxratelocationnum )";
+
+my $addl_from = " $join_cust $join_loc $join_tax_loc ";
my $where = "WHERE _date >= $beginning AND _date <= $ending ";
@@ -76,65 +80,87 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$where .= ' AND cust_main.agentnum = '. $agent->agentnum;
}
+# my ( $location_sql, @location_param ) = FS::cust_pkg->location_sql;
+# $where .= " AND $location_sql";
+#my @taxparam = ( 'itemdesc', @location_param );
+# now something along the lines of geocode matching ?
+#$where .= FS::cust_pkg->_location_sql_where('cust_tax_location');;
+my @taxparam = ( 'itemdesc', 'tax_rate_location.state', 'tax_rate_location.county', 'tax_rate_location.city', 'cust_bill_pkg_tax_rate_location.locationtaxid' );
+
+my $select = 'DISTINCT itemdesc,locationtaxid,tax_rate_location.state,tax_rate_location.county,tax_rate_location.city';
+
my $tax = 0;
my %taxes = ();
+my %basetaxes = ();
foreach my $t (qsearch({ table => 'cust_bill_pkg',
+ select => $select,
hashref => { pkgpart => 0 },
- addl_from => $join_cust,
+ addl_from => $addl_from,
extra_sql => $where,
})
)
{
- #warn $t->itemdesc. "\n";
+ my @params = map { my $f = $_; $f =~ s/.*\.//; $f } @taxparam;
+ my $label = join('~', map { $t->$_ } @params);
+ $label = 'Tax'. $label if $label =~ /^~/;
+ unless ( exists( $taxes{$label} ) ) {
+ my ($baselabel, @trash) = split /~/, $label;
- my $label = $t->itemdesc;
- $label ||= 'Tax';
- $taxes{$label}->{'label'} = $label;
- $taxes{$label}->{'url_param'} = "itemdesc=$label";
+ $taxes{$label}->{'label'} = join(', ', split(/~/, $label) );
+ $taxes{$label}->{'url_param'} =
+ join(';', map { "$_=". uri_escape($t->$_) } @params);
- # calculate total for this tax
- # calculate customer-exemption for this tax
- # calculate package-exemption for this tax
- # calculate monthly exemption (texas tax) for this tax
- # count up all the cust_tax_exempt_pkg records associated with
- # the actual line items.
-}
+ my $taxwhere = "FROM cust_bill_pkg $addl_from $where AND payby != 'COMP' ".
+ "AND ". join( ' AND ', map { "( $_ = ? OR ? = '' AND $_ IS NULL)" } @taxparam );
+ my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
+ " $taxwhere AND cust_bill_pkg.pkgnum = 0";
-foreach my $t (qsearch({ table => 'cust_bill_pkg',
- select => 'DISTINCT itemdesc',
- hashref => { pkgpart => 0 },
- addl_from => $join_cust,
- extra_sql => $where,
- })
- )
-{
+ my $x = scalar_sql($t, [ map { $_, $_ } @params ], $sql );
+ $tax += $x;
+ $taxes{$label}->{'tax'} += $x;
+
+ unless ( exists( $taxes{$baselabel} ) ) {
- my $label = $t->itemdesc;
- $label ||= 'Tax';
- my @taxparam = ( 'itemdesc' );
- my $taxwhere = "$from_join_cust $where AND payby != 'COMP' ".
- "AND itemdesc = ?" ;
+ $basetaxes{$baselabel}->{'label'} = $baselabel;
+ $basetaxes{$baselabel}->{'url_param'} = "itemdesc=$baselabel";
+ $basetaxes{$baselabel}->{'base'} = 1;
- my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
- " $taxwhere AND pkgnum = 0";
+ }
- my $x = scalar_sql($t, \@taxparam, $sql );
- $tax += $x;
- $taxes{$label}->{'tax'} += $x;
+ $basetaxes{$baselabel}->{'tax'} += $x;
+
+ }
+ # calculate customer-exemption for this tax
+ # calculate package-exemption for this tax
+ # calculate monthly exemption (texas tax) for this tax
+ # count up all the cust_tax_exempt_pkg records associated with
+ # the actual line items.
}
+
#ordering
-my @taxes =
- map $taxes{$_},
- sort { ($b cmp $a) }
- keys %taxes;
+my @taxes = ();
+
+foreach my $tax ( sort { $a cmp $b } keys %taxes ) {
+ my ($base, @trash) = split '~', $tax;
+ my $basetax = delete( $basetaxes{$base} );
+ if ($basetax) {
+ if ( $basetax->{tax} == $taxes{$tax}->{tax} ) {
+ $taxes{$tax}->{base} = 1;
+ } else {
+ push @taxes, $basetax;
+ }
+ }
+ push @taxes, $taxes{$tax};
+}
push @taxes, {
'label' => 'Total',
'url_param' => '',
'tax' => $tax,
+ 'base' => 1,
};
#--
@@ -143,7 +169,6 @@ push @taxes, {
#to FS::Report or FS::Record or who the fuck knows where)
sub scalar_sql {
my( $r, $param, $sql ) = @_;
- #warn "$sql\n";
my $sth = dbh->prepare($sql) or die dbh->errstr;
$sth->execute( map $r->$_(), @$param )
or die "Unexpected error executing statement $sql: ". $sth->errstr;
diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi
index 27dbcbf..ce928b8 100644
--- a/httemplate/search/report_prepaid_income.cgi
+++ b/httemplate/search/report_prepaid_income.cgi
@@ -38,22 +38,46 @@ my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time;
$now =~ /^(\d+)$/ or die "unparsable date?";
$now = $1;
+my @where = ();
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ my $agentnum = $1;
+ push @where, "agentnum = $agentnum";
+}
+
+#here is the agent virtualization
+push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+my $where = join(' AND ', @where);
+$where = "AND $where" if $where;
+
my( $total, $total_legacy ) = ( 0, 0 );
my @cust_bill_pkg =
grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ }
- qsearch( 'cust_bill_pkg', {
- 'recur' => { op=>'!=', value=>0 },
- 'edate' => { op=>'>', value=>$now },
- }, );
+ qsearch({
+ 'select' => 'cust_bill_pkg.*',
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => ' LEFT JOIN cust_bill USING ( invnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {
+ 'recur' => { op=>'!=', value=>0 },
+ 'edate' => { op=>'>', value=>$now },
+ },
+ 'extra_sql' => $where,
+ });
my @cust_pkg =
grep { $_->part_pkg->recur != 0
&& $_->part_pkg->freq !~ /^([01]|\d+[dw])$/
}
- qsearch ( 'cust_pkg', {
- 'bill' => { op=>'>', value=>$now }
- } );
+ qsearch({
+ 'select' => 'cust_pkg.*',
+ 'table' => 'cust_pkg',
+ 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'bill' => { op=>'>', value=>$now } },
+ 'extra_sql' => $where,
+ });
foreach my $cust_bill_pkg ( @cust_bill_pkg) {
my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
index 81adb64..d707bd8 100644
--- a/httemplate/search/report_prepaid_income.html
+++ b/httemplate/search/report_prepaid_income.html
@@ -1,28 +1,46 @@
-<% include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report',
- '',
- '',
- '<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
- <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
- <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
- <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
- '
-) %>
-
- <FORM ACTION="report_prepaid_income.cgi" METHOD="GET">
- <TABLE>
- <TR>
- <TD>Prepaid income (unearned revenue) as of </TD>
- <TD>
- <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now">
- <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date">
- </TD>
- </TR>
- <TR>
- <TD>
- </TD>
- <TD><i>m/d/y</i></TD>
- </TR>
- </TABLE>
+<% include('/elements/header.html','Prepaid Income (Unearned Revenue) Report')%>
+
+<% include('/elements/init_calendar.html') %>
+
+<FORM ACTION="report_prepaid_income.cgi" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <TR>
+ <TD>As of </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now">
+ <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+ </TR>
+ <TR>
+ <TD>
+ </TD>
+ <TD><FONT SIZE="-1"><i>m/d/y</i></FONT></TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2>&nbsp;</TD>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <TR>
+ <TD COLSPAN=2>&nbsp;</TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2 ALIGN="center"><INPUT TYPE="submit" VALUE="Generate report"></TD>
+ </TR>
+
+</TABLE>
+
<SCRIPT TYPE="text/javascript">
Calendar.setup({
inputField: "date_text",
@@ -32,7 +50,7 @@
});
</SCRIPT>
-<INPUT TYPE="submit" VALUE="Generate report">
+</FORM>
<% include('/elements/footer.html') %>
<%init>
diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi
index 58d87fa..6df0161 100755
--- a/httemplate/search/report_receivables.cgi
+++ b/httemplate/search/report_receivables.cgi
@@ -1,159 +1,17 @@
-<% include( 'elements/search.html',
+<% include( 'elements/cust_main_dayranges.html',
'title' => 'Accounts Receivable Aging Summary',
- 'name' => 'customers',
- 'query' => $sql_query,
- 'count_query' => $count_sql,
- 'header' => [
- FS::UI::Web::cust_header(),
- '0-30',
- '30-60',
- '60-90',
- '90+',
- 'Total',
- ],
- 'footer' => [
- 'Total',
- ( map '',
- ( 1 ..
- scalar(FS::UI::Web::cust_header()-1)
- )
- ),
- sprintf( $money_char.'%.2f',
- $row->{'balance_0_30'} ),
- sprintf( $money_char.'%.2f',
- $row->{'balance_30_60'} ),
- sprintf( $money_char.'%.2f',
- $row->{'balance_60_90'} ),
- sprintf( $money_char.'%.2f',
- $row->{'balance_90_0'} ),
- sprintf( '<b>'. $money_char.'%.2f'. '</b>',
- $row->{'balance_0_0'} ),
- ],
- 'fields' => [
- \&FS::UI::Web::cust_fields,
- format_balance('0_30'),
- format_balance('30_60'),
- format_balance('60_90'),
- format_balance('90_0'),
- format_balance('0_0'),
- ],
- 'links' => [
- ( map { $_ ne 'Cust. Status' ? $clink : '' }
- FS::UI::Web::cust_header()
- ),
- '',
- '',
- '',
- '',
- '',
- ],
- #'align' => 'rlccrrrrr',
- 'align' => FS::UI::Web::cust_aligns(). 'rrrrr',
- #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ],
- #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ],
- 'size' => [ ( map '', FS::UI::Web::cust_header() ),
- #'-1', '', '', '', '', '', ],
- '', '', '', '', '', ],
- 'style' => [ FS::UI::Web::cust_styles(),
- #'b', '', '', '', '', 'b', ],
- '', '', '', '', 'b', ],
- 'color' => [
- FS::UI::Web::cust_colors(),
- '',
- '',
- '',
- '',
- '',
- ],
-
- )
+ 'range_sub' => \&balance,
+ )
%>
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
-
-my @ranges = (
- [ 0, 30 ],
- [ 30, 60 ],
- [ 60, 90 ],
- [ 90, 0 ],
- [ 0, 0 ],
-);
-
-my $owed_cols = join(',', map balance( @$_ ), @ranges );
-
-my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql;
-
-my $active_sql = FS::cust_pkg->active_sql;
-my $inactive_sql = FS::cust_pkg->inactive_sql;
-my $suspended_sql = FS::cust_pkg->suspended_sql;
-my $cancelled_sql = FS::cust_pkg->cancelled_sql;
-
-my $packages_cols = <<END;
- ( $select_count_pkgs ) AS num_pkgs_sql,
- ( $select_count_pkgs AND $active_sql ) AS active_pkgs,
- ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs,
- ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs,
- ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs
-END
-
-my @where = ();
-
-unless ( $cgi->param('all_customers') ) {
-
- my $days = 0;
- if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) {
- $days = $1;
- }
-
- push @where, balance($days, 0, 'no_as'=>1). ' > 0'; # != 0';
-
-}
-
-if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
- my $agentnum = $1;
- push @where, "agentnum = $agentnum";
-}
-
-#here is the agent virtualization
-push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
-
-my $where = join(' AND ', @where);
-$where = "WHERE $where" if $where;
-
-my $count_sql = "select count(*) from cust_main $where";
-
-my $sql_query = {
- 'table' => 'cust_main',
- 'hashref' => {},
- 'select' => join(',',
- #'cust_main.*',
- 'custnum',
- $owed_cols,
- $packages_cols,
- FS::UI::Web::cust_sql_fields(),
- ),
- 'extra_sql' => $where,
- 'order_by' => "order by coalesce(lower(company), ''), lower(last)",
-};
-
-my $total_sql = "SELECT ". join(',', map balance( @$_, 'sum'=>1 ), @ranges).
- " FROM cust_main $where";
-
-my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
-$total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr;
-my $row = $total_sth->fetchrow_hashref();
-
-my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+ unless $FS::CurrentUser::CurrentUser->access_right('Receivables report')
+ or $FS::CurrentUser::CurrentUser->access_right('Financial reports');
</%init>
<%once>
-my $conf = new FS::Conf;
-
-my $money_char = $conf->config('money_char') || '$';
-
#Example:
#
# my $balance = balance(
@@ -170,9 +28,7 @@ my $money_char = $conf->config('money_char') || '$';
# )
sub balance {
- my($start, $end, %opt) = @_;
-
- my $as = $opt{'no_as'} ? '' : " AS balance_${start}_$end";
+ my($start, $end) = @_; #, %opt ?
#handle start and end ranges (86400 = 24h * 60m * 60s)
my $str2time = str2time_sql;
@@ -180,18 +36,10 @@ sub balance {
$start = $start ? "( $str2time now() $closing - ".($start * 86400). ' )' : '';
$end = $end ? "( $str2time now() $closing - ".($end * 86400). ' )' : '';
- $opt{'unapplied_date'} = 1;
-
- ( $opt{sum} ? 'SUM( ' : '' ).
- FS::cust_main->balance_date_sql( $start, $end, %opt ).
- ( $opt{sum} ? ' )' : '' ).
- $as;
+ #$opt{'unapplied_date'} = 1;
-}
+ FS::cust_main->balance_date_sql( $start, $end, 'unapplied_date'=>1,);
-sub format_balance { #closures help alot
- my $range = shift;
- sub { sprintf( $money_char.'%.2f', shift->get("balance_$range") ) };
}
</%once>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
index 19b1ee7..bfb0169 100755
--- a/httemplate/search/report_receivables.html
+++ b/httemplate/search/report_receivables.html
@@ -11,6 +11,11 @@
</TR>
<% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Customer Status'
+ )
+ %>
<TR>
<TD ALIGN="right">Customers</TD>
@@ -30,6 +35,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+ unless $FS::CurrentUser::CurrentUser->access_right('Receivables report')
+ or $FS::CurrentUser::CurrentUser->access_right('Financial reports');
</%init>
diff --git a/httemplate/search/report_svc_phone.html b/httemplate/search/report_svc_phone.html
new file mode 100644
index 0000000..2d64340
--- /dev/null
+++ b/httemplate/search/report_svc_phone.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 BGCOLOR="#e8e8e8" 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 a7630dd..e89c665 100755
--- a/httemplate/search/report_tax.cgi
+++ b/httemplate/search/report_tax.cgi
@@ -1,4 +1,4 @@
-<% include("/elements/header.html", "$agentname Sales Tax Report - ".
+<% include("/elements/header.html", "$agentname Tax Report - ".
( $beginning
? time2str('%h %o %Y ', $beginning )
: ''
@@ -44,12 +44,11 @@
% foreach my $region ( @regions ) {
%
% my $link = '';
-% if ( $region->{'label'} ne 'Total' ) {
-% if ( $region->{'label'} eq $out ) {
-% $link = ';out=1';
-% } else {
-% $link = ';'. $region->{'url_param'};
-% }
+% if ( $region->{'label'} eq $out ) {
+% $link = ';out=1';
+% } else {
+% $link = ';'. $region->{'url_param'}
+% if $region->{'url_param'};
% }
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -111,8 +110,12 @@
</TD>
% unless ( $cgi->param('show_taxclasses') ) {
+% my $invlink = $region->{'url_param_inv'}
+% ? ';'. $region->{'url_param_inv'}
+% : $link;
+
<<%$tdh%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;istax=1"
+ <A HREF="<% $baselink. $invlink %>;istax=1"
><% &$money_sprintf( $region->{'tax'} ) %></A>
</TD>
% }
@@ -138,13 +141,12 @@
% foreach my $region ( @base_regions ) {
%
% my $link = '';
-% #if ( $region->{'label'} ne 'Total' ) {
-% if ( $region->{'label'} eq $out ) {
-% $link = ';out=1';
-% } else {
-% $link = ';'. $region->{'url_param'};
-% }
-% #}
+% if ( $region->{'label'} eq $out ) {
+% $link = ';out=1';
+% } else {
+% $link = ';'. $region->{'url_param'}
+% if $region->{'url_param'};
+% }
%
% if ( $bgcolor eq $bgcolor1 ) {
% $bgcolor = $bgcolor2;
@@ -174,7 +176,7 @@
<<%$td%>>Total</TD>
<<%$td%> ALIGN="right">
<A HREF="<% $baselink %>;istax=1"
- ><% &$money_sprintf( $tax ) %></A>
+ ><% &$money_sprintf( $tot_tax ) %></A>
</TD>
</TR>
@@ -275,8 +277,6 @@ if ( $conf->exists('tax-pkg_address') ) {
"WHERE 0 < ( SELECT COUNT(*) FROM cust_main WHERE $gotcust LIMIT 1 )";
}
-my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0 );
-my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0, 0 );
my $out = 'Out of taxable region(s)';
my %regions = ();
@@ -289,6 +289,9 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
my $label = getlabel($r);
$regions{$label}->{'label'} = $label;
+
+ $regions{$label}->{$_} = $r->$_() for (qw( county state country )); #taxname?
+
$regions{$label}->{'url_param'} =
join(';', map "$_=".uri_escape($r->$_()),
qw( county state country taxname )
@@ -304,6 +307,8 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
$regions{$label}->{'url_param'} .= ';taxclass='. uri_escape($r->taxclass);
#no, always# if $cgi->param('show_taxclasses');
+ $regions{$label}->{'taxclass'} = $r->taxclass;
+
} else {
$regions{$label}->{'url_param'} .= ';taxclassNULL=1'
@@ -326,10 +331,9 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
my $t_sql =
"SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax";
my $t = scalar_sql($r, \@param, $t_sql);
- $total += $t;
$regions{$label}->{'total'} += $t;
- #if ( $label eq $out ) {# && $t ) {
+ #if ( $label eq $out ) # && $t ) {
# warn "adding $t for ".
# join('/', map $r->$_, qw( taxclass county state country ) ). "\n";
# #warn $t_sql if $r->state eq 'FL';
@@ -351,12 +355,27 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
# );
# }
+ #false laziness -ish w/report_tax.cgi
+ my $cust_exempt;
+ if ( $r->taxname ) {
+ my $q_taxname = dbh->quote($r->taxname);
+ $cust_exempt =
+ "( tax = 'Y'
+ OR EXISTS ( SELECT 1 FROM cust_main_exemption
+ WHERE cust_main_exemption.custnum = cust_main.custnum
+ AND cust_main_exemption.taxname = $q_taxname
+ )
+ )
+ ";
+ } else {
+ $cust_exempt = " tax = 'Y' ";
+ }
+
my $x_cust = scalar_sql($r, \@param,
"SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur)
- $fromwhere AND $nottax AND tax = 'Y' "
+ $fromwhere AND $nottax AND $cust_exempt "
);
- $exempt_cust += $x_cust;
$regions{$label}->{'exempt_cust'} += $x_cust;
## calculate package-exemption for this region
@@ -384,7 +403,6 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
AND ( tax != 'Y' OR tax IS NULL )
"
);
- $exempt_pkg += $x_pkg;
$regions{$label}->{'exempt_pkg'} += $x_pkg;
## calculate monthly exemption (texas tax) for this region
@@ -399,20 +417,11 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county',
$join_cust_pkg
$mywhere"
);
-# if ( $x_monthly ) {
-# #warn $r->taxnum(). ": $x_monthly\n";
-# $taxable -= $x_monthly;
-# }
-
- $exempt_monthly += $x_monthly;
$regions{$label}->{'exempt_monthly'} += $x_monthly;
my $taxable = $t - $x_cust - $x_pkg - $x_monthly;
-
- $tot_taxable += $taxable;
$regions{$label}->{'taxable'} += $taxable;
- $owed += $taxable * ($r->tax/100);
$regions{$label}->{'owed'} += $taxable * ($r->tax/100);
if ( defined($regions{$label}->{'rate'})
@@ -474,20 +483,43 @@ my $_taxamount_sub = sub {
scalar_sql($r, \@taxparam, $sql );
};
+#tax-report_groups filtering
+my($group_op, $group_value) = ( '', '' );
+if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
+ ( $group_op, $group_value ) = ( $1, $2 );
+}
+my $group_test = sub {
+ my $label = shift;
+ return 1 unless $group_op; #in case we get called inadvertantly
+ if ( $label eq $out ) { #don't display "out of taxable region" in this case
+ 0;
+ } elsif ( $group_op eq '=' ) {
+ $label =~ /^$group_value/;
+ } elsif ( $group_op eq '!=' ) {
+ $label !~ /^$group_value/;
+ } else {
+ die "guru meditation #00de: group_op $group_op\n";
+ }
+};
+
+my $tot_tax = 0;
#foreach my $label ( keys %regions ) {
foreach my $r ( qsearch(\%qsearch) ) {
#warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n";
my $label = getlabel($r);
+ if ( $group_op ) {
+ next unless &{$group_test}($label);
+ }
#my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' ";
#my @param = @base_param;
my $x = &{$_taxamount_sub}($r);
- $tax += $x unless $cgi->param('show_taxclasses');
$regions{$label}->{'tax'} += $x;
+ $tot_tax += $x unless $cgi->param('show_taxclasses');
}
@@ -508,34 +540,86 @@ if ( $cgi->param('show_taxclasses') ) {
);
$base_regions{$base_label}->{'tax'} += $x;
- $tax += $x;
+ $tot_tax += $x;
}
}
+my @regions = keys %regions;
+
+#tax-report_groups filtering
+@regions = grep &{$group_test}($_), @regions
+ if $group_op;
+
+#calculate totals
+my( $total, $tot_taxable, $tot_owed ) = ( 0, 0, 0 );
+my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0, 0 );
+my %taxclasses = ();
+my %county = ();
+my %state = ();
+my %country = ();
+foreach (@regions) {
+ $total += $regions{$_}->{'total'};
+ $tot_taxable += $regions{$_}->{'taxable'};
+ $tot_owed += $regions{$_}->{'owed'};
+ $exempt_cust += $regions{$_}->{'exempt_cust'};
+ $exempt_pkg += $regions{$_}->{'exempt_pkg'};
+ $exempt_monthly += $regions{$_}->{'exempt_monthly'};
+ $taxclasses{$regions{$_}->{'taxclass'}} = 1
+ if $regions{$_}->{'taxclass'};
+ $county{$regions{$_}->{'county'}} = 1;
+ $state{$regions{$_}->{'state'}} = 1;
+ $country{$regions{$_}->{'country'}} = 1;
+}
+
+my $total_url_param = '';
+my $total_url_param_invoiced = '';
+if ( $group_op ) {
+
+ my @country = keys %country;
+ warn "WARNING: multiple countries on this grouped report; total links broken"
+ if scalar(@country) > 1;
+ my $country = $country[0];
+
+ my @state = keys %state;
+ warn "WARNING: multiple countries on this grouped report; total links broken"
+ if scalar(@state) > 1;
+ my $state = $state[0];
+
+ $total_url_param_invoiced =
+ $total_url_param =
+ 'report_group='.uri_escape("$group_op $group_value").';'.
+ join(';', map 'taxclass='.uri_escape($_), keys %taxclasses );
+ $total_url_param .= ';'.
+ "country=$country;state=".uri_escape($state).';'.
+ join(';', map 'county='.uri_escape($_), keys %county ) ;
+
+}
#ordering
-my @regions =
+@regions =
map $regions{$_},
sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
- keys %regions;
+ @regions;
my @base_regions =
map $base_regions{$_},
sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
keys %base_regions;
+#add total line
push @regions, {
'label' => 'Total',
- 'url_param' => '',
+ 'url_param' => $total_url_param,
+ 'url_param_inv' => $total_url_param_invoiced,
'total' => $total,
'exempt_cust' => $exempt_cust,
'exempt_pkg' => $exempt_pkg,
'exempt_monthly' => $exempt_monthly,
'taxable' => $tot_taxable,
'rate' => '',
- 'owed' => $owed,
- 'tax' => $tax,
+ 'owed' => $tot_owed,
+ 'tax' => $tot_tax,
};
#--
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
index e5ffa9a..217f481 100755
--- a/httemplate/search/report_tax.html
+++ b/httemplate/search/report_tax.html
@@ -4,29 +4,49 @@
<TABLE>
+% if ( $conf->config('tax-report_groups') ) {
+% my @lines = $conf->config('tax-report_groups');
+
+ <TR>
+ <TD ALIGN="right">Tax group</TD>
+ <TD>
+ <SELECT NAME="report_group">
+
+ <OPTION VALUE="">all</OPTION>
+
+% foreach my $line ( @lines ) {
+% $line =~ /^\s*(.+)\s+(=|!=)\s+(.*)\s*$/ #or next;
+% or do { warn "bad report_group line: $line\n"; next; };
+% my($label, $op, $value) = ($1, $2, $3);
+
+ <OPTION VALUE="<% "$op $value" %>"><% $label %></OPTION>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+
+% }
+
<% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
<% include( '/elements/tr-input-beginning_ending.html' ) %>
-% my $conf = new FS::Conf;
-% if ( $conf->exists('enable_taxclasses') ) {
-%
+% if ( $conf->exists('enable_taxclasses') ) {
<TR>
<TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD>
<TD>Show tax classes</TD>
</TR>
% }
-% my @pkg_class = qsearch('pkg_class', {});
-% if ( @pkg_class ) {
-%
+% my @pkg_class = qsearch('pkg_class', {});
+% if ( @pkg_class ) {
<TR>
<TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD>
<TD>Show package classes</TD>
</TR>
% }
-
</TABLE>
<BR><INPUT TYPE="submit" VALUE="Get Report">
@@ -39,4 +59,6 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $conf = new FS::Conf;
+
</%init>
diff --git a/httemplate/search/report_unapplied_cust_pay.html b/httemplate/search/report_unapplied_cust_pay.html
new file mode 100755
index 0000000..10093e5
--- /dev/null
+++ b/httemplate/search/report_unapplied_cust_pay.html
@@ -0,0 +1,41 @@
+<% include('/elements/header.html', 'Unapplied Payments Aging Summary' ) %>
+%# 'Prepaid Balance Aging Summary', #???
+
+<FORM NAME="OneTrueForm" ACTION="unapplied_cust_pay.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+ <TR>
+ <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+ <FONT SIZE="+1">Search options</FONT>
+ </TH>
+ </TR>
+
+ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
+
+ <% include( '/elements/tr-select-cust_main-status.html',
+ 'label' => 'Customer Status'
+ )
+ %>
+
+ <TR>
+ <TD ALIGN="right">Customers</TD>
+ <TD>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">All customers (even those without unapplied payments)<BR>
+ <INPUT TYPE="radio" NAME="all_customers" VALUE="0" CHECKED onClick="if ( ! this.checked ) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">Customers with unapplied payments over <INPUT NAME="days" TYPE="text" SIZE=4 MAXLENGTH=3 VALUE="0"> days old
+ </TD>
+ </TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="Get Report">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi
index 2cb0c1e..d0b1029 100755
--- a/httemplate/search/svc_broadband.cgi
+++ b/httemplate/search/svc_broadband.cgi
@@ -111,13 +111,13 @@ foreach my $router (qsearch('router', {})) {
}
}
-my $link = [ $p.'view/svc_broadband.cgi', 'svcnum' ];
+my $link = [ $p.'view/svc_broadband.cgi?', 'svcnum' ];
#XXX get the router link working
my $link_router = sub { my $routernum = $routerbyblock{shift->blocknum}->routernum;
[ $p.'view/router.cgi?'.$routernum, 'routernum' ];
};
-my $link_cust = [ $p.'view/cust_main.cgi', 'custnum' ];
+my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ];
</%init>
diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi
index 2710d75..f061754 100755
--- a/httemplate/search/svc_external.cgi
+++ b/httemplate/search/svc_external.cgi
@@ -1,153 +1,135 @@
-%die "access denied"
-% unless $FS::CurrentUser::CurrentUser->access_right('List services');
-%
-%my $conf = new FS::Conf;
-%
-%my @svc_external = ();
-%my @h_svc_external = ();
-%my $sortby=\*svcnum_sort;
-%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
-%
-% @svc_external=qsearch('svc_external',{});
-%
-% if ( $cgi->param('magic') eq 'unlinked' ) {
-% @svc_external = grep { qsearchs('cust_svc', {
-% 'svcnum' => $_->svcnum,
-% 'pkgnum' => '',
-% }
-% )
-% }
-% @svc_external;
-% }
-%
-% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
-% my $sortby = $1;
-% if ( $sortby eq 'id' ) {
-% $sortby = \*id_sort;
-% }
-% }
-%
-%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
-%
-% @svc_external =
-% qsearch( 'svc_external', {}, '',
-% " WHERE $1 = ( SELECT svcpart FROM cust_svc ".
-% " WHERE cust_svc.svcnum = svc_external.svcnum ) "
-% );
-%
-%} elsif ( $cgi->param('title') =~ /^(.*)$/ ) {
-% $sortby=\*id_sort;
-% @svc_external=qsearch('svc_external',{ title => $1 });
-% if( $cgi->param('history') == 1 ) {
-% @h_svc_external=qsearch('h_svc_external',{ title => $1 });
-% }
-%} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) {
-% my $id = $1;
-% @svc_external = qsearchs('svc_external',{'id'=>$id});
-%}
-%
-%if ( scalar(@svc_external) == 1 ) {
-%
-%
-<% $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %>
-%
-%
-%} elsif ( scalar(@svc_external) == 0 ) {
-%
-%
-<% include('/elements/header.html', 'External Search Results' ) %>
-
- No matching external services found
-% } else {
-%
-%
-<% include('/elements/header.html', 'External Search Results', '') %>
-
- <% scalar(@svc_external) %> matching external services found
- <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
- <TR>
- <TH>Service #</TH>
- <TH><% FS::Msgcat::_gettext('svc_external-id') || 'External&nbsp;ID' %></TH>
- <TH><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH>
- </TR>
-%
-% foreach my $svc_external (
-% sort $sortby (@svc_external)
-% ) {
-% my($svcnum, $id, $title)=(
-% $svc_external->svcnum,
-% $svc_external->id,
-% $svc_external->title,
-% );
-%
-% my $rowspan = 1;
-%
-% print <<END;
-% <TR>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD>
-%END
-%
-% #print @rows;
-% print "</TR>";
-%
-% }
-% if( scalar(@h_svc_external) > 0 ) {
-% print <<HTML;
-% </TABLE>
-% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
-% <TR>
-% <TH>Freeside ID</TH>
-% <TH>Service #</TH>
-% <TH>Title</TH>
-% <TH>Date</TH>
-% </TR>
-%HTML
-%
-% foreach my $h_svc ( @h_svc_external ) {
-% my($svcnum, $id, $title, $user, $date)=(
-% $h_svc->svcnum,
-% $h_svc->id,
-% $h_svc->title,
-% $h_svc->history_user,
-% $h_svc->history_date,
-% );
-% my $rowspan = 1;
-% my ($h_cust_svc) = qsearchs( 'h_cust_svc', {
-% svcnum => $svcnum,
-% });
-% my $cust_pkg = qsearchs( 'cust_pkg', {
-% pkgnum => $h_cust_svc->pkgnum,
-% });
-% my $custnum = $cust_pkg->custnum;
-%
-% print <<END;
-% <TR>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></TD>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$svcnum</A></TD>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$title</A></TD>
-% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$date</A></TD>
-% </TR>
-%END
-% }
-% }
-%
-% print <<END;
-% </TABLE>
-% </BODY>
-%</HTML>
-%END
-%
-%}
-%
-%sub svcnum_sort {
-% $a->getfield('svcnum') <=> $b->getfield('svcnum');
-%}
-%
-%sub id_sort {
-% $a->getfield('id') <=> $b->getfield('id');
-%}
-%
-%
+<% include( 'elements/search.html',
+ 'title' => 'External service search results',
+ 'name' => 'external services',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'redirect' => $redirect,
+ 'header' => [ '#',
+ 'Service',
+ ( FS::Msgcat::_gettext('svc_external-id') || 'External ID' ),
+ ( FS::Msgcat::_gettext('svc_external-title') || 'Title' ),
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [ 'svcnum',
+ 'svc',
+ 'id',
+ 'title',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $link,
+ $link,
+ $link,
+ $link,
+ ( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'rlrr'.
+ FS::UI::Web::cust_aligns(),
+ 'color' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ '',
+ '',
+ '',
+ '',
+ FS::UI::Web::cust_styles(),
+ ],
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my %svc_external;
+my @extra_sql = ();
+my $orderby = 'ORDER BY svcnum';
+
+my $link = [ "${p}view/svc_external.cgi?", 'svcnum' ];
+my $redirect = $link;
+
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ push @extra_sql, 'pkgnum IS NULL'
+ if $cgi->param('magic') eq 'unlinked';
+
+ if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+ my $sortby = $1;
+ $orderby = "ORDER BY $sortby";
+ }
+
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+
+ push @extra_sql, "svcpart = $1";
+
+} elsif ( $cgi->param('title') =~ /^(.*)$/ ) {
+
+ $svc_external{'title'} = $1;
+ $orderby = 'ORDER BY id';
+
+ # is this linked from anywhere???
+ # if( $cgi->param('history') == 1 ) {
+ # @h_svc_external=qsearch('h_svc_external',{ title => $1 });
+ # }
+
+} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) {
+
+ $svc_external{'id'} = $1;
+
+}
+
+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_external) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @extra_sql );
+}
+
+my $count_query = "SELECT COUNT(*) FROM svc_external $addl_from ";
+if ( keys %svc_external ) {
+ $count_query .= ' WHERE '.
+ join(' AND ', map "$_ = ". dbh->quote($svc_external{$_}),
+ keys %svc_external
+ );
+}
+$count_query .= $extra_sql;
+
+my $sql_query = {
+ 'table' => 'svc_external',
+ 'hashref' => \%svc_external,
+ 'select' => join(', ',
+ 'svc_external.*',
+ 'part_svc.svc',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => "$extra_sql $orderby",
+ 'addl_from' => $addl_from,
+};
+
+#smaller false laziness w/svc_*.cgi here
+my $link_cust = sub {
+ my $svc_x = shift;
+ $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : '';
+};
+
+
+</%init>
diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi
index 49340c6..21e1a92 100644
--- a/httemplate/search/svc_phone.cgi
+++ b/httemplate/search/svc_phone.cgi
@@ -3,33 +3,39 @@
'name' => 'phone numbers',
'query' => $sql_query,
'count_query' => $count_query,
- 'redirect' => $link,
+ 'redirect' => $redirect,
'header' => [ '#',
'Service',
'Country code',
'Phone number',
+ @header,
FS::UI::Web::cust_header(),
],
'fields' => [ 'svcnum',
'svc',
'countrycode',
'phonenum',
+ @fields,
\&FS::UI::Web::cust_fields,
],
'links' => [ $link,
$link,
$link,
$link,
+ ( map '', @header ),
( map { $_ ne 'Cust. Status' ? $link_cust : '' }
FS::UI::Web::cust_header()
),
],
- 'align' => 'rlrr'. FS::UI::Web::cust_aligns(),
+ 'align' => 'rlrr'.
+ join('', map 'r', @header).
+ FS::UI::Web::cust_aligns(),
'color' => [
'',
'',
'',
'',
+ ( map '', @header ),
FS::UI::Web::cust_colors(),
],
'style' => [
@@ -37,6 +43,7 @@
'',
'',
'',
+ ( map '', @header ),
FS::UI::Web::cust_styles(),
],
)
@@ -48,9 +55,16 @@ die "access denied"
my $conf = new FS::Conf;
-my $orderby = 'ORDER BY svcnum';
+my @select = ();
my %svc_phone = ();
my @extra_sql = ();
+my $orderby = 'ORDER BY svcnum';
+
+my @header = ();
+my @fields = ();
+my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ];
+my $redirect = $link;
+
if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
push @extra_sql, 'pkgnum IS NULL'
@@ -61,6 +75,50 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
$orderby = "ORDER BY $sortby";
}
+ if ( $cgi->param('usage_total') ) {
+
+ my($beginning,$ending) = FS::UI::Web::parse_beginning_ending($cgi, 'usage');
+
+ $redirect = '';
+
+ #my $and_date = " AND startdate >= $beginning AND startdate <= $ending ";
+ my $and_date = " AND enddate >= $beginning AND enddate <= $ending ";
+
+ my $fromwhere = " FROM cdr WHERE cdr.svcnum = svc_phone.svcnum $and_date";
+
+ #more efficient to join against cdr just once... this will do for now
+ push @select, map { " ( SELECT SUM($_) $fromwhere ) AS $_ " }
+ qw( billsec rated_price );
+
+ my $money_char = $conf->config('money_char') || '$';
+
+ push @header, 'Minutes', 'Billed';
+ push @fields,
+ sub { sprintf('%.3f', shift->get('billsec') / 60 ); },
+ sub { $money_char. sprintf('%.2f', shift->get('rated_price') ); };
+
+ #XXX and termination... (this needs a config to turn on, not by default)
+ if ( 1 ) { # $conf->exists('cdr-termination_hack') { #}
+
+ my $f_w =
+ " FROM cdr_termination LEFT JOIN cdr USING ( acctid ) ".
+ " WHERE cdr.acctid = svc_phone.phonenum ". # XXX connectone-specific
+ $and_date;
+
+ push @select,
+ " ( SELECT SUM(billsec) $f_w ) AS term_billsec ",
+ " ( SELECT SUM(cdr_termination.rated_price) $f_w ) AS term_rated_price";
+
+ push @header, 'Term Min', 'Term Billed';
+ push @fields,
+ sub { sprintf('%.3f', shift->get('term_billsec') / 60 ); },
+ sub { $money_char. sprintf('%.2f', shift->get('rated_price') ); };
+
+ }
+
+
+ }
+
} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
push @extra_sql, "svcpart = $1";
} else {
@@ -99,15 +157,14 @@ my $sql_query = {
'select' => join(', ',
'svc_phone.*',
'part_svc.svc',
- 'cust_main.custnum',
- FS::UI::Web::cust_sql_fields(),
+ @select,
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
),
'extra_sql' => "$extra_sql $orderby",
'addl_from' => $addl_from,
};
-my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ];
-
#smaller false laziness w/svc_*.cgi here
my $link_cust = sub {
my $svc_x = shift;
diff --git a/httemplate/search/unapplied_cust_pay.html b/httemplate/search/unapplied_cust_pay.html
new file mode 100755
index 0000000..8d064d1
--- /dev/null
+++ b/httemplate/search/unapplied_cust_pay.html
@@ -0,0 +1,28 @@
+<% include( 'elements/cust_main_dayranges.html',
+ #'title' => 'Prepaid Balance Aging Summary', #???
+ 'title' => 'Unapplied Payments Aging Summary',
+ 'range_sub' => \&unapplied_payments,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
+<%once>
+
+sub unapplied_payments {
+ my($start, $end, %opt) = @_;
+
+ #handle start and end ranges (86400 = 24h * 60m * 60s)
+ my $str2time = str2time_sql;
+ my $closing = str2time_sql_closing;
+ $start = $start ? "( $str2time now() $closing - ".($start * 86400). ' )' : '';
+ $end = $end ? "( $str2time now() $closing - ".($end * 86400). ' )' : '';
+
+ FS::cust_main->unapplied_payments_date_sql( $start, $end );
+
+}
+
+</%once>
diff --git a/httemplate/view/attachment.html b/httemplate/view/attachment.html
new file mode 100644
index 0000000..5fc0539
--- /dev/null
+++ b/httemplate/view/attachment.html
@@ -0,0 +1,16 @@
+<% $attach->body %>
+<%init>
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $attachnum = $1 or die 'Invalid attachment number';
+$FS::CurrentUser::CurrentUser->access_right('Download attachment') or die 'access denied';
+
+my $attach = qsearchs('cust_attachment', { attachnum => $attachnum }) or die "Attachment not found: $attachnum";
+die 'access denied' if $attach->disabled;
+
+$m->clear_buffer;
+$r->content_type($attach->mime_type || 'text/plain');
+$r->headers_out->add('Content-Disposition' => 'attachment;filename=' . $attach->filename);
+
+
+</%init>
diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi
index 09ac9a7..ad2ff54 100755
--- a/httemplate/view/cust_bill-logo.cgi
+++ b/httemplate/view/cust_bill-logo.cgi
@@ -10,7 +10,7 @@ my $conf = new FS::Conf;
my $templatename;
my $agentnum = '';
if ( $cgi->param('invnum') ) {
- $templatename = $cgi->param('templatename');
+ $templatename = $cgi->param('template') || $cgi->param('templatename');
my $cust_bill = qsearchs('cust_bill', { 'invnum' => $cgi->param('invnum') } )
or die 'unknown invnum';
$agentnum = $cust_bill->cust_main->agentnum;
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
index f09e1b7..51e47e0 100755
--- a/httemplate/view/cust_bill-pdf.cgi
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -4,11 +4,23 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
-#untaint invnum
+my( $invnum, $template, $notice_name );
my($query) = $cgi->keywords;
-$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
-my $templatename = $2;
-my $invnum = $3;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $invnum =~ s/\.pdf//i;
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
my $cust_bill = qsearchs({
'select' => 'cust_bill.*',
@@ -19,7 +31,7 @@ my $cust_bill = qsearchs({
});
die "Invoice #$invnum not found!" unless $cust_bill;
-my $pdf = $cust_bill->print_pdf( '', $templatename);
+my $pdf = $cust_bill->print_pdf(\%opt);
http_header('Content-Type' => 'application/pdf' );
http_header('Content-Length' => length($pdf) );
diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
index 5313dbf..881491f 100755
--- a/httemplate/view/cust_bill-ps.cgi
+++ b/httemplate/view/cust_bill-ps.cgi
@@ -1,14 +1,25 @@
-<% $cust_bill->print_ps( '', $templatename) %>
+<% $cust_bill->print_ps(\%opt) %>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
-#untaint invnum
+my( $invnum, $template, $notice_name );
my($query) = $cgi->keywords;
-$query =~ /^((.+)-)?(\d+)$/;
-my $templatename = $2;
-my $invnum = $3;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
my $cust_bill = qsearchs({
'select' => 'cust_bill.*',
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
index 450c74e..ce8d96a 100755
--- a/httemplate/view/cust_bill.cgi
+++ b/httemplate/view/cust_bill.cgi
@@ -2,10 +2,32 @@
"View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
)) %>
+% if ( $conf->exists('deleteinvoices')
+% && $curuser->access_right('Delete invoices' )
+% )
+% {
+
+ <SCRIPT TYPE="text/javascript">
+ function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <A HREF = "javascript:areyousure(
+ '<%$p%>misc/delete-cust_bill.html?<% $invnum %>',
+ 'Are you sure you want to delete this invoice?'
+ )"
+ TITLE = "Delete this invoice from the database completely"
+ >Delete this invoice</A>
+ <BR><BR>
+
+% }
% if ( $cust_bill->owed > 0
% && scalar( grep $payby{$_}, qw(BILL CASH WEST MCRD) )
-% && $FS::CurrentUser::CurrentUser->access_right('Post payment')
+% && $curuser->access_right('Post payment')
+% && ! $conf->exists('pkg-balances')
% )
% {
% my $s = 0;
@@ -36,27 +58,25 @@
% }
+% if ( $curuser->access_right('Resend invoices') ) {
-% if ( $FS::CurrentUser::CurrentUser->access_right('Resend invoices') ) {
-
- <A HREF="<% $p %>misc/print-invoice.cgi?<% $link %>">Re-print this invoice</A>
+ <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>">Re-print this invoice</A>
% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
- | <A HREF="<% $p %>misc/email-invoice.cgi?<% $link %>">Re-email this invoice</A>
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>">Re-email this invoice</A>
% }
% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
- | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $link %>">Re-fax this invoice</A>
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>">Re-fax this invoice</A>
% }
<BR><BR>
% }
-
% if ( $conf->exists('invoice_latex') ) {
- <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>.pdf">View typeset invoice</A>
+ <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>">View typeset invoice PDF</A>
<BR><BR>
% }
@@ -72,24 +92,35 @@
<% $br ? '<BR><BR>' : '' %>
% if ( $conf->exists('invoice_html') ) {
-
- <% join('', $cust_bill->print_html('', $templatename) ) %>
+ <% join('', $cust_bill->print_html(\%opt) ) %>
% } else {
-
- <PRE><% join('', $cust_bill->print_text('', $templatename) ) %></PRE>
+ <PRE><% join('', $cust_bill->print_text(\%opt) ) %></PRE>
% }
<% include('/elements/footer.html') %>
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+ unless $curuser->access_right('View invoices');
-#untaint invnum
+my( $invnum, $template, $notice_name );
my($query) = $cgi->keywords;
-$query =~ /^((.+)-)?(\d+)$/;
-my $templatename = $2;
-my $invnum = $3;
+if ( $query =~ /^((.+)-)?(\d+)$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = $cgi->param('notice_name');
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
my $conf = new FS::Conf;
@@ -104,7 +135,7 @@ my $cust_bill = qsearchs({
'table' => 'cust_bill',
'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
'hashref' => { 'invnum' => $invnum },
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
});
die "Invoice #$invnum not found!" unless $cust_bill;
@@ -113,8 +144,8 @@ my $display_custnum = $cust_bill->cust_main->display_custnum;
#my $printed = $cust_bill->printed;
-my $link = $templatename ? "$templatename-$invnum" : $invnum;
+my $link = "invnum=$invnum";
+$link .= ';template='. uri_escape($template) if $template;
+$link .= ';notice_name='. $notice_name if $notice_name;
</%init>
-
-
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index 2231d41..314207b 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -1,8 +1,19 @@
-<% include("/elements/header.html","Customer View: ". $cust_main->name ) %>
+<% include('/elements/header.html', {
+ 'title' => "Customer View: ". $cust_main->name,
+ 'nobr' => 1,
+ })
+%>
+<BR>
-% if ( $curuser->access_right('Edit customer') ) {
- <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> |
-% }
+<% include('/elements/menubar.html',
+ { 'newstyle' => 1,
+ 'selected' => $viewname{$view},
+ 'url_base' => $cgi->url. "?custnum=$custnum;show=",
+ },
+ %views,
+ )
+%>
+<BR>
<% include('/elements/init_overlib.html') %>
@@ -13,6 +24,12 @@ function areyousure(href, message) {
}
</SCRIPT>
+% if ( $view eq 'basics' || $view eq 'jumbo' ) {
+
+% if ( $curuser->access_right('Edit customer') ) {
+ <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> |
+% }
+
% if ( $curuser->access_right('Cancel customer')
% && $cust_main->ncancelled_pkgs
% ) {
@@ -23,6 +40,7 @@ function areyousure(href, message) {
'actionlabel' => 'Confirm Cancellation',
'color' => '#ff0000',
'cust_main' => $cust_main,
+ 'width' => 616, #make room for reasons
}
)
%> |
@@ -74,10 +92,12 @@ function areyousure(href, message) {
</TD>
</TR>
</TABLE>
-%
-%if ( $cust_main->comments =~ /[^\s\n\r]/ ) {
-%
+% }
+
+% if ( $view eq 'notes' || $view eq 'jumbo' ) {
+
+%if ( $cust_main->comments =~ /[^\s\n\r]/ ) {
<BR>
Comments
<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
@@ -87,12 +107,17 @@ Comments
</TD>
</TR>
</TABLE></TABLE>
-% }
<BR><BR>
+% }
+<A NAME="notes">
% my $notecount = scalar($cust_main->notes());
% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) {
-<A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR>
+% unless ( $view eq 'notes' && $cust_main->comments !~ /[^\s\n\r]/ ) {
+ <BR>
+ <A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR>
+% }
+
% if ( $curuser->access_right('Add customer note') &&
% ! $conf->exists('cust_main-disable_notes')
% ) {
@@ -114,25 +139,78 @@ Comments
<% include('cust_main/notes.html', 'custnum' => $cust_main->custnum ) %>
% }
+<BR>
+% if(! $conf->config('disable_cust_attachment')
+% and $curuser->access_right('Add attachment')) {
+<% include( '/elements/popup_link-cust_main.html',
+ 'label' => 'Attach file',
+ 'action' => $p.'edit/cust_main_attach.cgi',
+ 'actionlabel' => 'Upload file',
+ 'cust_main' => $cust_main,
+ 'width' => 616,
+ 'height' => 408,
+ )
+%>
+% }
+<% include('cust_main/attachments.html', 'custnum' => $cust_main->custnum ) %>
+% if($cgi->param('show_deleted')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . '#notes'
+ %>"><I>(Show active attachments)</I></A>
+% }
+% elsif($curuser->access_right('View deleted attachments')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . ';show_deleted=1#notes'
+ %>"><I>(Show deleted attachments)</I></A>
+% }
+<BR>
-% if ( $conf->config('ticket_system') ) {
+% }
- <BR><BR>
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A><BR>
+% }
+
+% if ( $view eq 'tickets' || $view eq 'jumbo' ) {
+
+% if ( $conf->config('ticket_system') ) {
<% include('cust_main/tickets.html', $cust_main ) %>
% }
+ <BR><BR>
+
+% }
+% if ( $view eq 'jumbo' ) { #XXX enable me && $curuser->access_right('View customer packages') {
-<BR><BR>
+ <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A><BR>
+% }
+
+% if ( $view eq 'packages' || $view eq 'jumbo' ) {
% #XXX enable me# if ( $curuser->access_right('View customer packages') {
<% include('cust_main/packages.html', $cust_main ) %>
% #}
+% }
+
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
+% }
+
+% if ( $view eq 'payment_history' || $view eq 'jumbo' ) {
+
% if ( $conf->config('payby-default') ne 'HIDE' ) {
<% include('cust_main/payment_history.html', $cust_main ) %>
% }
+% }
+
+% if ( $view eq 'change_history' ) { # || $view eq 'jumbo'
+<% include('cust_main/change_history.html', $cust_main ) %>
+% }
<% include('/elements/footer.html') %>
<%init>
@@ -144,10 +222,16 @@ die "access denied"
my $conf = new FS::Conf;
-die "No customer specified (bad URL)!" unless $cgi->keywords;
-my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
-$query =~ /^(\d+)$/;
-my $custnum = $1;
+my $custnum;
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+} else {
+ die "No customer specified (bad URL)!" unless $cgi->keywords;
+ my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+}
+
my $cust_main = qsearchs( {
'table' => 'cust_main',
'hashref' => { 'custnum' => $custnum },
@@ -155,4 +239,22 @@ my $cust_main = qsearchs( {
});
die "Customer not found!" unless $cust_main;
+#false laziness w/pref/pref.html and Conf.pm (cust_main-default_view)
+tie my %views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+;
+$views{'Tickets'} = 'tickets'
+ if $conf->config('ticket_system');
+$views{'Packages'} = 'packages';
+$views{'Payment History'} = 'payment_history'
+ unless $conf->config('payby-default' eq 'HIDE');
+$views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$views{'Jumbo'} = 'jumbo';
+
+my %viewname = reverse %views;
+
+my $view = $cgi->param('show') || $curuser->default_customer_view;
+
</%init>
diff --git a/httemplate/view/cust_main/attachments.html b/httemplate/view/cust_main/attachments.html
new file mode 100755
index 0000000..53635fd
--- /dev/null
+++ b/httemplate/view/cust_main/attachments.html
@@ -0,0 +1,151 @@
+% if ( scalar(@attachments) ) {
+
+ <% include('/elements/init_overlib.html') %>
+
+ <% include("/elements/table-grid.html") %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+% if ( $conf->exists('cust_main_note-display_times') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Filename</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Size</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+% if($cgi->param('show_deleted')) {
+% if ($curuser->access_right('View deleted attachments')) {
+% @attachments = grep { $_->disabled } @attachments;
+% }
+% else {
+% @attachments = ();
+% }
+% }
+% else {
+% @attachments = grep { not $_->disabled } @attachments;
+% }
+%
+% foreach my $attach (@attachments) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $pop = popurl(3);
+% my $attachnum = $attach->attachnum;
+% my $edit = '';
+% if($attach->disabled) { # then you can undelete it or purge it.
+% if ($curuser->access_right('Undelete attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "undelete=1",
+% 'Undelete attachment');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(undelete)</A>!;
+% }
+% if ($curuser->access_right('Purge attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "purge=1",
+% 'Purge attachment');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(purge)</A>!;
+% }
+% }
+% else { # you can download or edit it
+% if ($curuser->access_right('Edit attachment') ) {
+% my $clickjs = popup('edit/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum",
+% 'Edit attachment properties');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(edit)</A>!;
+% }
+% if($curuser->access_right('Delete attachment') ) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "delete=1",
+% 'Delete attachment');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(delete)</A>!;
+% }
+% if ($curuser->access_right('Download attachment') ) {
+% $edit .= qq!&nbsp; <A HREF="!.popurl(1).'attachment.html?'.$attachnum.qq!">(download)</A>!;
+% }
+% }
+
+ <TR>
+ <% note_datestr($attach,$conf,$bgcolor) %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->otaker%>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->filename %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->mime_type %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% size_units( $attach->size ) %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $edit %>
+ </TD>
+ </TR>
+
+% } #end display notes
+
+</TABLE>
+
+% }
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my(%opt) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+die "Customer not found!" unless $cust_main;
+
+my (@attachments) = qsearch('cust_attachment', {'custnum' => $custnum});
+
+#subroutines
+
+sub note_datestr {
+ my($note, $conf, $bgcolor) = @_ or return '';
+ my $td = qq{<TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">};
+ my $format = "$td%b&nbsp;%o,&nbsp;%Y</TD>";
+ $format .= "$td%l:%M%P</TD>"
+ if $conf->exists('cust_main_note-display_times');
+ ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub size_units {
+ my $bytes = shift;
+ return $bytes if $bytes < 1024;
+ return int($bytes / 1024)."K" if $bytes < 1048576;
+ return int($bytes / 1048576)."M";
+}
+
+sub popup {
+ my ($url, $label) = @_;
+ my $onclick =
+ include('/elements/popup_link_onclick.html',
+ 'action' => popurl(2).$url,
+ 'actionlabel' => $label,
+ 'width' => 616,
+ 'height' => 408,
+ 'frame' => 'top',
+ );
+ return qq!onclick="$onclick"!;
+}
+
+
+</%init>
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index aea90e8..c8d0c47 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -159,11 +159,24 @@ Billing information
</TR>
% }
-
+% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
<TR>
- <TD ALIGN="right">Tax&nbsp;exempt</TD>
+ <TD ALIGN="right">Tax&nbsp;exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
<TD BGCOLOR="#ffffff"><% $cust_main->tax ? 'yes' : 'no' %></TD>
</TR>
+% foreach my $exempt_group ( @exempt_groups ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;exempt (<% $exempt_group %> taxes)</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax_exemption($exempt_group) ? 'yes' : 'no' %></TD>
+</TR>
+% }
+
+% if ( $conf->exists('enable_taxproducts') ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;location</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->geocode('cch') %></TD>
+</TR>
+% }
<TR>
<TD ALIGN="right">Postal&nbsp;invoices</TD>
<TD BGCOLOR="#ffffff">
@@ -203,6 +216,20 @@ Billing information
</TR>
% }
+% if ( $conf->exists('voip-cust_email_csv_cdr') ) {
+ <TR>
+ <TD ALIGN="right">Email&nbsp;CDRs&nbsp;as&nbsp;CSV</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->email_csv_cdr ? 'yes' : 'no' %></TD>
+ </TR>
+% }
+
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+ <TR>
+ <TD ALIGN="right">CDR termination settlement</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->cdr_termination_percentage %><% $cust_main->cdr_termination_percentage =~ /\d/ ? '%' : '' %></TD>
+ </TR>
+% }
+
</TABLE></TD></TR></TABLE>
<%once>
@@ -217,4 +244,10 @@ my @invoicing_list = $cust_main->invoicing_list;
my $conf = new FS::Conf;
my $money_char = $conf->config('money_char') || '$';
+#false laziness w/edit/cust_main/billing.html
+my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+my $term_sth = dbh->prepare($term_sql) or die dbh->errstr;
+$term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+my $show_term = $term_sth->fetchrow_arrayref->[0];
+
</%init>
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
new file mode 100644
index 0000000..53a79f4
--- /dev/null
+++ b/httemplate/view/cust_main/change_history.html
@@ -0,0 +1,302 @@
+% if ( int( time - (keys %years)[0] * 31556736 ) > $start ) {
+ Show:
+% my $chy = $cgi->param('change_history-years');
+% foreach my $y (keys %years) {
+% if ( $y == $years ) {
+ <FONT SIZE="+1"><% $years{$y} %></FONT>
+% } else {
+% $cgi->param('change_history-years', $y);
+ <A HREF="<% $cgi->self_url %>"><% $years{$y} %></A>
+% }
+% last if int( time - $y * 31556736 ) < $start;
+% }
+% $cgi->param('change_history-years', $chy);
+% }
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">User</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Item</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH>
+ <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
+% )
+% {
+%
+% my $history_other = '';
+% my $act = $item->history_action;
+% if ( $act =~ /^replace/ ) {
+% my $pkey = $item->primary_key;
+% my $date = $item->history_date;
+% $history_other = qsearchs({
+% 'table' => $item->table,
+% 'hashref' => { $pkey => $item->$pkey(),
+% 'history_action' => $replace_other{$act},
+% 'historynum' => { 'op' => $replace_dir{$act},
+% 'value' => $item->historynum
+% },
+% },
+% 'extra_sql' => "
+% AND history_date $replace_direq{$act} $date
+% AND ($date $replace_op{$act} $fuzz) $replace_direq{$act} history_date
+% ORDER BY historynum $replace_ord{$act} LIMIT 1
+% ",
+% });
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $otaker = $item->history_user;
+% $otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+% $otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+% $otaker = '<i>job queue</i>' if $otaker eq 'fs_queue';
+ <% $otaker %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $d = time2str('%b %o, %Y', $item->history_date );
+% $d =~ s/ /&nbsp;/g;
+ <% $d %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $t = time2str('%r', $item->history_date );
+% $t =~ s/ /&nbsp;/g;
+ <% $t %>
+ </TD>
+ <TD ALIGN="center" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $label = $h_tables{$item->table};
+% $label = &{ $h_table_labelsub{$item->table} }( $item, $label )
+% if $h_table_labelsub{$item->table};
+ <% $label %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $action{$item->history_action} %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% join(', ',
+ map { my $value = ( $_ =~ /(^pay(info|cvv)|^ss|_password)$/ )
+ ? 'N/A'
+ : $item->get($_);
+ $value = substr($value, 0, 77).'...' if length($value) > 80;
+ $value = encode_entities($value);
+ "<I>$_</I>:<B>$value</B>";
+ }
+ grep { $history_other
+ ? ( $item->get($_) ne $history_other->get($_) )
+ : ( $item->get($_) =~ /\S/ )
+ }
+ grep { ! /^(history|custnum$)/i }
+ $item->fields
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+<%once>
+
+# length-switching
+
+tie my %years, 'Tie::IxHash',
+ .5 => '6 months',
+ 1 => '1 year',
+ 2 => '2 years',
+ 5 => '5 years',
+ 39 => 'all history',
+;
+
+# labeling history rows
+
+my %action = (
+ 'insert' => 'Insert', #'Create',
+ 'replace_old' => 'Change&nbsp;from',
+ 'replace_new' => 'Change&nbsp;to',
+ 'delete' => 'Remove',
+);
+
+# finding the other replace row
+
+my %replace_other = (
+ 'replace_new' => 'replace_old',
+ 'replace_old' => 'replace_new',
+);
+my %replace_dir = (
+ 'replace_new' => '<',
+ 'replace_old' => '>',
+);
+my %replace_direq = (
+ 'replace_new' => '<=',
+ 'replace_old' => '>=',
+);
+my %replace_op = (
+ 'replace_new' => '-',
+ 'replace_old' => '+',
+);
+my %replace_ord = (
+ 'replace_new' => 'DESC',
+ 'replace_old' => 'ASC',
+);
+
+my $fuzz = 5; #seems like a lot
+
+# which tables to search and what to call them
+
+tie my %tables, 'Tie::IxHash',
+ 'cust_main' => 'Customer',
+ 'cust_main_invoice' => 'Invoice destination',
+ 'cust_pkg' => 'Package',
+ #? or just svc_* ? 'cust_svc' =>
+ 'svc_acct' => 'Account',
+ 'radius_usergroup' => 'RADIUS group',
+ 'svc_domain' => 'Domain',
+ 'svc_www' => 'Hosting',
+ 'svc_forward' => 'Mail forward',
+ 'svc_broadband' => 'Broadband',
+ 'svc_external' => 'External service',
+ 'svc_phone' => 'Phone',
+ 'phone_device' => 'Phone device',
+ #? it gets provisioned anyway 'phone_avail' => 'Phone',
+;
+
+my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+
+my %table_join = (
+ 'svc_acct' => $svc_join,
+ 'radius_usergroup' => $svc_join,
+ 'svc_domain' => $svc_join,
+ 'svc_www' => $svc_join,
+ 'svc_forward' => $svc_join,
+ 'svc_broadband' => $svc_join,
+ 'svc_external' => $svc_join,
+ 'svc_phone' => $svc_join,
+ 'phone_device' => $svc_join,
+);
+
+my %h_tables = map { ( "h_$_" => $tables{$_} ) } keys %tables;
+
+my %pkgpart = ();
+my $pkg_labelsub = sub {
+ my($item, $label) = @_;
+ $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+ $label. ': <b>'. encode_entities($pkgpart{$item->pkgpart}). '</b>';
+};
+
+my $svc_labelsub = sub {
+ my($item, $label) = @_;
+ $label. ': <b>'. encode_entities($item->label($item->history_date)). '</b>';
+};
+
+my %h_table_labelsub = (
+ 'h_cust_pkg' => $pkg_labelsub,
+ 'h_svc_acct' => $svc_labelsub,
+ #'h_radius_usergroup' =>
+ 'h_svc_domain' => $svc_labelsub,
+ 'h_svc_www' => $svc_labelsub,
+ 'h_svc_forward' => $svc_labelsub,
+ 'h_svc_broadband' => $svc_labelsub,
+ 'h_svc_external' => $svc_labelsub,
+ 'h_svc_phone' => $svc_labelsub,
+ #'h_phone_device'
+);
+
+# cust_main
+# cust_main_invoice
+
+# cust_pkg
+# cust_pkg_option?
+# cust_pkg_detail
+# cust_pkg_reason? no
+
+#cust_svc
+#cust_svc_option?
+#svc_*
+# svc_acct
+# radius_usergroup
+# acct_snarf? is this even used?
+# svc_domain
+# domain_record
+# registrar
+# svc_forward
+# svc_www
+# svc_broadband
+# (virtual fields? eh... maybe when they're real)
+# svc_external
+# svc_phone
+# phone_device
+# phone_avail
+
+# future:
+
+# inventory_item (from services)
+# pkg_referral? (changed?)
+
+#random others:
+
+# cust_location?
+# cust_main-exemption?? (295.ca named tax exemptions)
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access deined"
+ unless $curuser->access_right('View customer history');
+
+# find out the beginning of this customer history, if possible
+my $h_insert = qsearchs({
+ 'table' => 'h_cust_main',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ 'history_action' => 'insert',
+ },
+ 'extra_sql' => 'ORDER BY historynum LIMIT 1',
+});
+my $start = $h_insert ? $h_insert->history_date : 0;
+
+# retreive the history
+
+my @history = ();
+
+my $years = $conf->config('change_history-years') || .5;
+if ( $cgi->param('change_history-years') =~ /^([\d\.]+)$/ ) {
+ $years = $1;
+}
+my $newer_than = int( time - $years * 31556736 ); #60*60*24*365.24
+
+local($FS::Record::nowarn_classload) = 1;
+
+foreach my $table ( keys %tables ) {
+ my @items = qsearch({
+ 'table' => "h_$table",
+ 'addl_from' => $table_join{$table},
+ 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, },
+ 'extra_sql' => ' AND custnum = '. $cust_main->custnum,
+ });
+ push @history, @items;
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html
index 060da87..71e8d69 100644
--- a/httemplate/view/cust_main/misc.html
+++ b/httemplate/view/cust_main/misc.html
@@ -85,13 +85,24 @@
</TR>
% if ( $conf->exists('cust_main-enable_birthdate') ) {
-% my $dt = DateTime->from_epoch(epoch => $cust_main->birthdate,
-% time_zone=>'floating',
-% );
+% my $dt = $cust_main->birthdate ne ''
+% ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate,
+% 'time_zone' =>'floating',
+% )
+% : '';
<TR>
<TD ALIGN="right">Date of Birth</TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->birthdate ne '' ? $dt->strftime($date_format) : '' %></TD>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+ </TR>
+
+% }
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ <TR>
+ <TD ALIGN="right">Census tract</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->censustract %></TD>
</TR>
% }
diff --git a/httemplate/view/cust_main/one_time_charge_link.html b/httemplate/view/cust_main/one_time_charge_link.html
new file mode 100644
index 0000000..4ce8a28
--- /dev/null
+++ b/httemplate/view/cust_main/one_time_charge_link.html
@@ -0,0 +1,91 @@
+<SCRIPT TYPE="text/javascript">
+
+function taxproductmagic(which) {
+
+ var str = '';
+ var elements = which.form.elements;
+ for (var i = 0; i<elements.length; i++) {
+
+ if (elements[i].name == 'taxproductnum'){
+ document.getElementById('taxproductnum').value = elements[i].value;
+ continue;
+ }
+ if (elements[i].name == 'taxproductnum_description'){
+ continue;
+ }
+
+ if (str.length){str += ';';}
+
+ var value = '';
+ if ( elements[i].type == 'checkbox' || elements[i].type == 'radio' ) {
+ if ( elements[i].checked == true ) {
+ value = elements[i].value;
+ //} else {
+ // value = '';
+ }
+ } else {
+ value = elements[i].value;
+ }
+ str += elements[i].name + '=' + escape(value);
+
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=taxproductnum&onclick=taxproductquickchargemagic&taxproductnum='+document.getElementById('taxproductnum').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxproductquickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxproductnum;taxproductnum=';
+ str += escape(document.getElementById('taxproductnum').value);
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+function taxoverridemagic(which) {
+ var str = '';
+ var elements = which.ownerDocument.QuickChargeForm.elements;
+ for (var i = 0; i<elements.length; i++) {
+ if (elements[i].name == 'tax_override'){
+ document.getElementById('tax_override').value = elements[i].value;
+ continue;
+ }
+ if (str.length){str += ';';}
+ str += elements[i].name + '=' + escape(elements[i].value);
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/part_pkg_taxoverride.html?element_name=tax_override;onclick=taxoverridequickchargemagic;selected='+document.getElementById('tax_override').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxoverridequickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxoverride;tax_override=';
+ str += document.getElementById('tax_override').value;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+</SCRIPT>
+
+<FORM NAME='quickcharge' STYLE="margin:0; padding:0; display:inline"><INPUT NAME="taxproductnum" ID="taxproductnum" TYPE="hidden"><INPUT NAME="tax_override" ID="tax_override" TYPE="hidden"><INPUT NAME="charge_storage" ID="charge_storage" TYPE="hidden"><INPUT NAME="taxproductnum_description" ID="taxproductnum_description" TYPE="hidden"></FORM>
+
+<% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/quick-charge.html?custnum='. $cust_main->custnum,
+ 'label' => 'One-time charge',
+ 'actionlabel' => 'One-time charge',
+ 'color' => '#333399',
+ 'width' => 763,
+ 'height' => 408,
+ })
+%>
+
+<%init>
+
+my($cust_main) = @_;
+
+</%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index 2c25888..17a0691 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -1,94 +1,24 @@
-<A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A><BR>
-
-% if ( $curuser->access_right('One-time charge') ) {
-
-<SCRIPT TYPE="text/javascript">
-
-function taxproductmagic(which) {
- var str = '';
- var elements = which.form.elements;
- for (var i = 0; i<elements.length; i++) {
- if (elements[i].name == 'taxproductnum'){
- document.getElementById('taxproductnum').value = elements[i].value;
- continue;
- }
- if (elements[i].name == 'taxproductnum_description'){
- continue;
- }
- if (str.length){str += ';';}
- str += elements[i].name + '=' + escape(elements[i].value);
- }
- document.getElementById('charge_storage').value = str;
- cClick();
- overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=taxproductnum&onclick=taxproductquickchargemagic&taxproductnum='+document.getElementById('taxproductnum').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
-}
-
-function taxproductquickchargemagic() {
- var str = document.getElementById('charge_storage').value;
- if (str.length){str += ';';}
- str += 'magic=taxproductnum;taxproductnum=';
- str += escape(document.getElementById('taxproductnum').value);
- cClick();
- overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
-
-}
-
-function taxoverridemagic(which) {
- var str = '';
- var elements = which.ownerDocument.QuickChargeForm.elements;
- for (var i = 0; i<elements.length; i++) {
- if (elements[i].name == 'tax_override'){
- document.getElementById('tax_override').value = elements[i].value;
- continue;
- }
- if (str.length){str += ';';}
- str += elements[i].name + '=' + escape(elements[i].value);
- }
- document.getElementById('charge_storage').value = str;
- cClick();
- overlib( OLiframeContent('<% $p %>/edit/part_pkg_taxoverride.html?element_name=tax_override;onclick=taxoverridequickchargemagic;selected='+document.getElementById('tax_override').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
-}
-
-function taxoverridequickchargemagic() {
- var str = document.getElementById('charge_storage').value;
- if (str.length){str += ';';}
- str += 'magic=taxoverride;tax_override=';
- str += document.getElementById('tax_override').value;
- cClick();
- overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
-
-}
-
-</SCRIPT>
-<FORM NAME='quickcharge'>
- <INPUT NAME="taxproductnum" ID="taxproductnum" TYPE="hidden">
- <INPUT NAME="tax_override" ID="tax_override" TYPE="hidden">
- <INPUT NAME="charge_storage" ID="charge_storage" TYPE="hidden">
- <INPUT NAME="taxproductnum_description" ID="taxproductnum_description" TYPE="hidden">
-</FORM>
-% }
-
% my $s = 0;
% if ( $curuser->access_right('Order customer package') ) {
<% $s++ ? ' | ' : '' %>
- <% order_pkg_link($cust_main) %>
+ <% include( '/elements/popup_link-cust_main.html',
+ 'action' => $p. 'misc/order_pkg.html',
+ 'label' => 'Order&nbsp;new&nbsp;package',
+ 'actionlabel' => 'Order new package',
+ 'color' => '#333399',
+ 'cust_main' => $cust_main,
+ 'closetext' => 'Close',
+ 'width' => 763,
+ 'height' => 350,
+ )
+ %>
% }
% if ( $curuser->access_right('One-time charge')
% && $conf->config('payby-default') ne 'HIDE'
% ) {
-%
<% $s++ ? ' | ' : '' %>
- <% include('/elements/popup_link.html',
- {
- 'action' => $p. 'edit/quick-charge.html?custnum='. $cust_main->custnum,
- 'label' => 'One-time charge',
- 'actionlabel' => 'One-time charge',
- 'color' => '#333399',
- 'width' => 763,
- 'height' => 408,
- })
- %>
+ <% include('one_time_charge_link.html', $cust_main) %>
% }
% if ( $curuser->access_right('Bulk change customer packages') ) {
@@ -109,19 +39,25 @@ Current packages
% )
% )
% {
+% my $prev = $cgi->param('showcancelledpackages');
% $cgi->param('showcancelledpackages', 1);
-%
-
( <a href="<% $cgi->self_url %>">show
+% $cgi->param('showcancelledpackages', $prev);
% } else {
% $cgi->param('showcancelledpackages', 0);
-%
-
( <a href="<% $cgi->self_url %>">hide
+% $cgi->param('showcancelledpackages', 1);
% }
cancelled packages</a> )
% }
+% if ( $num_old_packages ) {
+% $cgi->param('showoldpackages', 1);
+ ( <a href="<% $cgi->self_url %>">show old packages</a> )
+% } elsif ( $cgi->param('showoldpackages') ) {
+% $cgi->param('showoldpackages', 0);
+ ( <a href="<% $cgi->self_url %>">hide old packages</a> )
+% }
% if ( @$packages ) {
<% include('/elements/table-grid.html') %>
@@ -138,6 +74,7 @@ Current packages
<TH CLASS="grid" BGCOLOR="#cccccc">Services</TH>
</TR>
+% #$FS::cust_pkg::DEBUG = 2;
% foreach my $cust_pkg (@$packages) {
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -146,25 +83,13 @@ Current packages
% $bgcolor = $bgcolor1;
% }
%
-% my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
% my %iopt = (
-% 'bgcolor' => $bgcolor,
-% 'cust_pkg' => $cust_pkg,
-% 'part_pkg' => $cust_pkg->part_pkg,
-%
-% #for services.html and status.html
-% 'cust_pkg-display_times' => $conf->exists('cust_pkg-display_times'),
-%
-% #for location.html
-% 'countrydefault' => $countrydefault,
-% 'statedefault' => ( scalar($conf->config('statedefault'))
-% || ($countrydefault eq 'US' ? 'CA' : '') ),
-%
-% #for services.html
-% 'svc_external-skip_manual' => $conf->exists('svc_external-skip_manual'),
-% 'legacy_link' => $conf->exists('legacy_link'),
-%
+% 'bgcolor' => $bgcolor,
+% 'cust_pkg' => $cust_pkg,
+% 'part_pkg' => $cust_pkg->part_pkg,
+% %conf_opt,
% );
+%
<!--pkgnum: <% $cust_pkg->pkgnum %>-->
<TR>
@@ -193,6 +118,7 @@ Current packages
if ( el ) el.scrollIntoView(true);
</SCRIPT>
% }
+
<%init>
my( $cust_main ) = @_;
@@ -200,18 +126,38 @@ my $conf = new FS::Conf;
my $curuser = $FS::CurrentUser::CurrentUser;
-my $packages = get_packages($cust_main, $conf);
+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';
+my %conf_opt = (
+ #for services.html and status.html
+ 'cust_pkg-display_times' => $conf->exists('cust_pkg-display_times'),
+
+ #for status.html
+ 'cust_pkg-show_autosuspend' => $conf->exists('cust_pkg-show_autosuspend'),
+ #for status.html pkg-balances
+ 'pkg-balances' => $conf->exists('pkg-balances'),
+ 'money_char' => ( $conf->config('money_char') || '$' ),
+
+ #for location.html
+ 'countrydefault' => $countrydefault,
+ 'statedefault' => ( scalar($conf->config('statedefault'))
+ || ($countrydefault eq 'US' ? 'CA' : '') ),
+ #for services.html
+ 'svc_external-skip_manual' => $conf->exists('svc_external-skip_manual'),
+ 'legacy_link' => $conf->exists('legacy_link'),
+ 'svc_broadband-manage_link' => $conf->config('svc_broadband-manage_link'),
+);
+
#subroutines
sub get_packages {
my $cust_main = shift or return undef;
my $conf = shift;
-
- my @packages = ();
+
my $method;
if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
|| ( $conf->exists('hidecancelledpackages')
@@ -223,19 +169,51 @@ sub get_packages {
$method = 'all_pkgs';
}
- [ $cust_main->$method() ];
-}
+ my $cust_pkg_fields =
+ join(', ', map { "cust_pkg.$_ AS $_" } fields('cust_pkg') );
+
+ my $part_pkg_fields =
+ join(', ', map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') );
+
+ my $group_by =
+ join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
+ join(', ', map "part_pkg.$_", fields('part_pkg') );
+
+ my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
+ ' WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
+
+ my @packages = $cust_main->$method( {
+ 'select' => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+ } );
+ my $num_old_packages = scalar(@packages);
+
+ foreach my $cust_pkg ( @packages ) {
+ my %hash = $cust_pkg->hash;
+ my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
+ grep { /^part_pkg_/ } keys %hash;
+ $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
+ }
+
+ unless ( $cgi->param('showoldpackages') ) {
+ my $years = $conf->config('cust_main-packages-years') || 2;
+ my $seconds = 31556926; #60*60*24*365.2422 is close enough
+ my $then = time - $seconds;
+
+ my %hide = ( 'cancelled' => 'cancel',
+ 'one-time charge' => 'setup',
+ );
+
+ @packages =
+ grep { !exists($hide{$_->status}) or $_->get($hide{$_->status}) > $then
+ or $_->num_svcs #don't hide packages w/services
+ }
+ @packages;
+ }
+
+ $num_old_packages -= scalar(@packages);
-sub order_pkg_link {
- include( '/elements/popup_link-cust_main.html',
- 'action' => $p. 'misc/order_pkg.html',
- 'label' => 'Order&nbsp;new&nbsp;package',
- 'actionlabel' => 'Order new package',
- 'color' => '#333399',
- 'cust_main' => shift,
- 'closetext' => 'Close',
- 'width' => 763,
- )
+ ( \@packages, $num_old_packages );
}
</%init>
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index b07e1af..280a016 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -6,7 +6,7 @@
ID ="cust_pkg<% $cust_pkg->pkgnum %>"
><% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B></A>
-
- <% $part_pkg->comment |h %>
+ <% $part_pkg->custom_comment |h %>
</TD>
</TR>
@@ -38,7 +38,7 @@
%
% if ( $curuser->access_right('Customize customer package') ) {
% $br=1;
- (&nbsp;<%pkg_customize_link($cust_pkg,$cust_pkg->custnum)%>&nbsp;)
+ (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
% }
%
<% $br ? '<BR>' : '' %>
@@ -58,18 +58,18 @@
% my $editi = $curuser->access_right('Edit customer package invoice details');
% my $editc = $curuser->access_right('Edit customer package comments');
+% my @cust_pkg_detail = $cust_pkg->cust_pkg_detail;
+% my @invoice_detail = grep { $_->detailtype eq 'I' } @cust_pkg_detail;
+% my @comments = grep { $_->detailtype eq 'C' } @cust_pkg_detail;
%
-% if ( $cust_pkg->cust_pkg_detail('I')
-% || $cust_pkg->cust_pkg_detail('C')
-% || $editi
-% || $editc ) {
+% if ( scalar(@invoice_detail) || scalar(@comments) || $editi || $editc ) {
%
% my $editlink = $p. 'edit/cust_pkg_detail?pkgnum='. $cust_pkg->pkgnum.
% ';detailtype=';
<TR>
-% if ( $cust_pkg->cust_pkg_detail('I') ) {
+% if ( @invoice_detail ) {
<TD VALIGN="top">
<% include('/elements/table-grid.html') %>
<TR>
@@ -89,7 +89,7 @@
</FONT>
</TH>
</TR>
-% foreach my $cust_pkg_detail ( $cust_pkg->cust_pkg_detail('I') ) {
+% foreach my $cust_pkg_detail ( @invoice_detail ) {
<TR>
<TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
</TR>
@@ -113,7 +113,7 @@
</TD>
% }
-% if ( $cust_pkg->cust_pkg_detail('C') ) {
+% if ( @comments ) {
<TD VALIGN="top">
<% include('/elements/table-grid.html') %>
<TR>
@@ -133,7 +133,7 @@
</FONT>
</TH>
</TR>
-% foreach my $cust_pkg_detail ( $cust_pkg->cust_pkg_detail('C') ) {
+% foreach my $cust_pkg_detail ( @comments ) {
<TR>
<TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
</TR>
@@ -198,9 +198,10 @@ sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', 'Edit&nbsp;dates', @_ ); }
sub pkg_customize_link {
my $cust_pkg = shift or return '';
+ my $part_pkg = shift;
my $custnum = $cust_pkg->custnum;
qq!<A HREF="${p}edit/part_pkg.cgi?!.
- "clone=". $cust_pkg->part_pkg->pkgpart. ';'.
+ "clone=". $part_pkg->pkgpart. ';'.
"pkgnum=". $cust_pkg->pkgnum.
qq!">Customize</A>!;
}
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
index 1e47373..0fe7931 100644
--- a/httemplate/view/cust_main/packages/services.html
+++ b/httemplate/view/cust_main/packages/services.html
@@ -36,15 +36,29 @@
% )
% ) {
(&nbsp;<%svc_recharge_link($cust_svc)%>&nbsp;)
-% }
+% }
</FONT></TD>
- <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px">
-% if ( $curuser->access_right('Unprovision customer service') ) {
- (&nbsp;<%svc_unprovision_link($cust_svc)%>&nbsp;)
-% }
- </FONT></TD>
+% my $ip_addr = $cust_svc->svc_x->ip_addr;
+
+% if ( $part_svc->svcdb eq 'svc_broadband' ) {
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<% include('/elements/popup_link-ping.html', 'ip'=> $ip_addr ) %>&nbsp;)</FONT>
+
+% }
+
+% my $manage_link = $opt{'svc_broadband-manage_link'};
+% if ( $manage_link && $part_svc->svcdb eq 'svc_broadband' ) {
+% my $svc_manage_link = eval(qq("$manage_link"));
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<A HREF="<% $svc_manage_link %>">Manage Device</A>&nbsp;)</FONT>
+
+% }
+
+% if ( $curuser->access_right('Unprovision customer service') ) {
+ <FONT SIZE="-2">(&nbsp;<%svc_unprovision_link($cust_svc)%>&nbsp;)</FONT>
+% }
+ </TD>
</TR>
% }
@@ -75,6 +89,8 @@ my $cust_pkg = $opt{'cust_pkg'};
my $part_pkg = $opt{'part_pkg'};
my $curuser = $FS::CurrentUser::CurrentUser;
+my $conf = new FS::Conf;
+
sub svc_provision_link {
my ($cust_pkg, $part_svc, $opt, $curuser) = @_;
( my $svc_nbsp = $part_svc->svc ) =~ s/\s+/&nbsp;/g;
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index 106137b..6667a55 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -8,20 +8,21 @@
<% pkg_status_row($cust_pkg, 'Cancelled', 'cancel', 'color'=>'FF0000', %opt ) %>
- <% pkg_status_row_colspan(
+ <% pkg_status_row_colspan( $cust_pkg,
( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align' => 'right', 'color' => 'ff0000', 'size' => '-2',
+ 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt
)
%>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan('Never billed') %>
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt, ) %>
% } else {
<% pkg_status_row( $cust_pkg, 'Setup', 'setup', %opt ) %>
- <% pkg_status_row_changed( $cust_pkg, %opt ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
<% pkg_status_row_if( $cust_pkg, 'Suspended', 'susp', %opt, curuser=>$curuser ) %>
@@ -34,19 +35,20 @@
<% pkg_status_row( $cust_pkg, 'Suspended', 'susp', 'color'=>'FF9900', %opt ) %>
- <% pkg_status_row_colspan(
+ <% pkg_status_row_colspan( $cust_pkg,
( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
- 'align' => 'right', 'color' => 'FF9900', 'size' => '-2',
+ 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt,
)
%>
% unless ( $cust_pkg->get('setup') ) {
- <% pkg_status_row_colspan('Never billed') %>
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt ) %>
% } else {
<% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt ) %>
% }
- <% pkg_status_row_changed( $cust_pkg, %opt ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
% # pkg_status_row($cust_pkg, 'Next bill', 'bill', %opt)
<% pkg_status_row_if( $cust_pkg, 'Expires', 'expire', %opt, curuser=>$curuser ) %>
@@ -70,7 +72,15 @@
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan('Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)') %>
+ <% pkg_status_row_colspan( $cust_pkg, 'Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_if(
+ $cust_pkg,
+ ( $part_pkg->freq ? 'Start billing' : 'Bill on' ),
+ 'start_date',
+ %opt
+ )
+ %>
<TR>
<TD COLSPAN=<%$colspan%>>
@@ -84,7 +94,9 @@
% } else {
- <% pkg_status_row_colspan("Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')' ) %>
+ <% pkg_status_row_colspan($cust_pkg, "Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %>
% }
%
@@ -92,7 +104,7 @@
%
% unless ( $part_pkg->freq ) {
- <% pkg_status_row_colspan('One-time&nbsp;charge') %>
+ <% pkg_status_row_colspan($cust_pkg, 'One-time&nbsp;charge', '', 'colspan'=>$colspan, %opt ) %>
<% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %>
@@ -100,18 +112,20 @@
%
% if (scalar($cust_pkg->overlimit)) {
- <% pkg_status_row_colspan(
+ <% pkg_status_row_colspan( $cust_pkg,
'Overlimit',
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color' => 'FFD000',
+ 'color'=>'FFD000', 'colspan'=>$colspan,
+ %opt
)
%>
% } else {
- <% pkg_status_row_colspan(
+ <% pkg_status_row_colspan( $cust_pkg,
'Active',
$billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
- 'color' => '00CC00',
+ 'color'=>'00CC00', 'colspan'=>$colspan,
+ %opt
)
%>
% }
@@ -122,12 +136,12 @@
%
% }
%
-% if ( $conf->exists('cust_pkg-show_autosuspend') ) {
+% if ( $opt{'cust_pkg-show_autosuspend'} ) {
% my $autosuspend = pkg_autosuspend_time( $cust_pkg );
% $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend;
% }
- <% pkg_status_row_changed( $cust_pkg, %opt ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
<% 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, 'Will automatically suspend by', 'autosuspend', %opt) %>
@@ -165,13 +179,10 @@
</TABLE>
</TD>
-
<%init>
my %opt = @_;
-my $conf = new FS::Conf;
-
my $bgcolor = $opt{'bgcolor'};
my $cust_pkg = $opt{'cust_pkg'};
my $part_pkg = $opt{'part_pkg'};
@@ -216,8 +227,15 @@ sub pkg_status_row {
$html .= qq(<FONT COLOR="#$color"><B>) if length($color);
$html .= qq($title&nbsp;);
$html .= qq(</B></FONT>) if length($color);
+
+ if ( $opt{'pkg_balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
$html .= qq(</TD>);
- $html .= pkg_datestr($cust_pkg, $field, %opt).'</TR>';
+ $html .= pkg_datestr($cust_pkg, $field, %opt). '</TR>';
$html;
}
@@ -240,20 +258,31 @@ sub pkg_status_row_if {
sub pkg_status_row_changed {
my( $cust_pkg, %opt ) = @_;
+
return '' unless $cust_pkg->change_date;
- my $html = pkg_status_row( $cust_pkg, 'Package&nbsp;changed', 'change_date', %opt );
+
+ my $html =
+ pkg_status_row( $cust_pkg, 'Package&nbsp;changed', 'change_date', %opt );
+
my $old = $cust_pkg->old_cust_pkg;
if ( $old ) {
my $part_pkg = $old->part_pkg;
my $label = 'Changed from '. $cust_pkg->change_pkgnum. ': '.
- $part_pkg->pkg. ' - '. $part_pkg->comment;
- $html .= pkg_status_row_colspan( $label, '', size=>'-1', align=>'right' );
+ $part_pkg->pkg_comment(nopartpkg => 1);
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
+ 'size' => '-1',
+ 'align' => 'right',
+ 'colspan' => $opt{'colspan'},
+ );
}
+
$html;
}
sub pkg_status_row_colspan {
- my($title, $addl, %opt) = @_;
+ my($cust_pkg, $title, $addl, %opt) = @_;
+
+ my $colspan = $opt{'colspan'};
my $align = $opt{'align'} ? 'ALIGN="'. $opt{'align'}.'"' : '';
my $color = $opt{'color'} ? 'COLOR="#'.$opt{'color'}.'"' : '';
@@ -266,6 +295,13 @@ sub pkg_status_row_colspan {
$html .= qq(</B>) if $color && !$size;
$html .= qq(</FONT>) if length($color) || $size;
$html .= ",&nbsp;$addl" if length($addl);
+
+ if ( $opt{'pkg-balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
$html .= qq(</TD></TR>);
$html;
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 335ce24..2ac3f26 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -1,5 +1,3 @@
-<BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
-
%# payment links
% my $s = 0;
@@ -129,10 +127,33 @@
%# tax exemption link
-% if ( $curuser->access_right('View customer tax exemptions') ) {
- <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A>
+% my $view_exemptions = $curuser->access_right('View customer tax exemptions');
+% my $add_adjustment = ( $conf->exists('enable_tax_adjustments')
+% && $curuser->access_right('Add customer tax adjustment')
+% );
+% if ( $view_exemptions || $add_adjustment ) {
+
+% if ( $view_exemptions ) {
+ <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A>
+ <% $add_adjustment ? '|' : '' %>
+% }
+
+% if ( $add_adjustment ) {
+ <% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum,
+ 'label' => 'Add tax adjustment',
+ 'actionlabel' => 'Add tax adjustment',
+ #'color' => '#333399',
+ #'width' => 763,
+ 'height' => 200,
+ })
+ %>
+ |
+ <A HREF="<% $p %>search/cust_tax_adjustment.html?custnum=<% $custnum %>">View tax adjustments</A>
+% }
+
<BR>
-% }
+% }
%# batched payment links
@@ -353,13 +374,14 @@ my %status = (
#get payment history
my @history = ();
-my %opt =
+my %opt = (
( map { $_ => scalar($conf->config($_)) }
qw( card_refund-days )
),
( map { $_ => $conf->exists($_) }
- qw( deletepayments deleterefunds )
- );
+ qw( deleteinvoices deletepayments deleterefunds pkg-balances )
+ )
+);
#invoices
foreach my $cust_bill ($cust_main->cust_bill) {
@@ -370,6 +392,15 @@ foreach my $cust_bill ($cust_main->cust_bill) {
};
}
+#statements
+foreach my $cust_statement ($cust_main->cust_statement) {
+ push @history, {
+ 'date' => $cust_statement->_date,
+ 'desc' => include('payment_history/statement.html', $cust_statement, %opt ),
+ #'charge' => $cust_bill->charged,
+ };
+}
+
#payments (some false laziness w/credits)
foreach my $cust_pay ($cust_main->cust_pay) {
push @history, {
@@ -384,7 +415,7 @@ foreach my $cust_pay ($cust_main->cust_pay) {
foreach my $cust_pay_void ($cust_main->cust_pay_void) {
push @history, {
'date' => $cust_pay_void->_date,
- 'desc' => include('payment_history/voided_payment.html', $cust_pay_void),
+ 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
'void_payment' => $cust_pay_void->paid,
};
@@ -394,7 +425,7 @@ foreach my $cust_pay_void ($cust_main->cust_pay_void) {
foreach my $cust_credit ($cust_main->cust_credit) {
push @history, {
'date' => $cust_credit->_date,
- 'desc' => include('payment_history/credit.html', $cust_credit),
+ 'desc' => include('payment_history/credit.html', $cust_credit, %opt ),
'credit' => $cust_credit->amount,
};
diff --git a/httemplate/view/cust_main/payment_history/credit.html b/httemplate/view/cust_main/payment_history/credit.html
index 2deb275..058c6f5 100644
--- a/httemplate/view/cust_main/payment_history/credit.html
+++ b/httemplate/view/cust_main/payment_history/credit.html
@@ -9,7 +9,13 @@ my $curuser = $FS::CurrentUser::CurrentUser;
my @cust_credit_bill = $cust_credit->cust_credit_bill;
my @cust_credit_refund = $cust_credit->cust_credit_refund;
-my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_credit->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_credit->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
if ( scalar(@cust_credit_bill) == 0
&& scalar(@cust_credit_refund) == 0 ) {
#completely unapplied
@@ -45,15 +51,15 @@ if ( scalar(@cust_credit_bill) == 0
&& scalar(@cust_credit_refund) == 0
&& $cust_credit->credited == 0 ) {
#applied to one invoice, the usual situation
- $desc = ' '. $cust_credit_bill[0]->applied_to_invoice;
+ $desc .= ' '. $cust_credit_bill[0]->applied_to_invoice;
} elsif ( scalar(@cust_credit_bill) == 0
&& scalar(@cust_credit_refund) == 1
&& $cust_credit->credited == 0 ) {
#applied to one refund
- $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date);
+ $desc .= ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date);
} else {
#complicated
- $desc = '<BR>';
+ $desc .= '<BR>';
foreach my $app ( sort { $a->_date <=> $b->_date }
( @cust_credit_bill, @cust_credit_refund ) ) {
if ( $app->isa('FS::cust_credit_bill') ) {
diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html
index 39c6739..c0d32df 100644
--- a/httemplate/view/cust_main/payment_history/invoice.html
+++ b/httemplate/view/cust_main/payment_history/invoice.html
@@ -1,9 +1,11 @@
-<% $link %><% $pre %>Invoice #<% $invnum %>
-(Balance $ <% $cust_bill->owed %>)<% $post %><% $link ? '</A>' : '' %><% $events %>
+<% $link %><% $pre %>Invoice #<% $cust_bill->display_invnum %>
+(Balance $ <% $cust_bill->owed %>)<% $post %><% $link ? '</A>' : '' %><% $delete %><% $events %>
<%init>
my( $cust_bill, %opt ) = @_;
+my $conf = new FS::Conf;
+
my $curuser = $FS::CurrentUser::CurrentUser;
my($pre, $post) = ('', '');
@@ -18,6 +20,15 @@ my $link = $curuser->access_right('View invoices')
? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
: '';
+my $delete = '';
+if ( $opt{'deleteinvoices'} && $curuser->access_right('Delete invoices') ) {
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_bill.html?$invnum',!.
+ qq!'Are you sure you want to delete this invoice?')"!.
+ qq! TITLE="Delete this invoice from the database completely"!.
+ qq!>delete</A>)!;
+}
+
my $events = '';
#1.9
if ( $cust_bill->num_cust_event
@@ -26,8 +37,8 @@ if ( $cust_bill->num_cust_event
)
) {
$events =
- qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=!.
- $cust_bill->invnum. '">(&nbsp;View invoice events&nbsp;)</A></FONT>';
+ qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=$invnum!.
+ '">(&nbsp;View invoice events&nbsp;)</A></FONT>';
}
#
diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
index 2e24b17..a4a349b 100644
--- a/httemplate/view/cust_main/payment_history/payment.html
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -32,7 +32,13 @@ $payby =~ s/^MCRD$/Manual credit card/;
$payby =~ s/^BILL$//;
my $info = $payby ? "($payby$payinfo)" : '';
-my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_pay->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
if ( scalar(@cust_bill_pay) == 0
&& scalar(@cust_pay_refund) == 0 ) {
#completely unapplied
@@ -68,15 +74,15 @@ if ( scalar(@cust_bill_pay) == 0
&& scalar(@cust_pay_refund) == 0
&& $cust_pay->unapplied == 0 ) {
#applied to one invoice, the usual situation
- $desc = ' '. $cust_bill_pay[0]->applied_to_invoice;
+ $desc .= ' '. $cust_bill_pay[0]->applied_to_invoice;
} elsif ( scalar(@cust_bill_pay) == 0
&& scalar(@cust_pay_refund) == 1
&& $cust_pay->unapplied == 0 ) {
#applied to one refund
- $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
+ $desc .= ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
} else {
#complicated
- $desc = '<BR>';
+ $desc .= '<BR>';
foreach my $app ( sort { $a->_date <=> $b->_date }
( @cust_bill_pay, @cust_pay_refund ) ) {
if ( $app->isa('FS::cust_bill_pay') ) {
diff --git a/httemplate/view/cust_main/payment_history/statement.html b/httemplate/view/cust_main/payment_history/statement.html
new file mode 100644
index 0000000..dedec9b
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/statement.html
@@ -0,0 +1,34 @@
+<% $link %><% $pre %>Statement #<% $statementnum %>
+%# (Balance $ <% $cust_statement->owed %>)
+<% $post %><% $link ? '</A>' : '' %><% $events %>
+<%init>
+
+my( $cust_statement, %opt ) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($pre, $post) = ('', '');
+#if ( $cust_statement->owed > 0 ) {
+# $pre = '<B><FONT SIZE="+1" COLOR="#FF0000">Open ';
+# $post = '</FONT></B>';
+#}
+
+my $statementnum = $cust_statement->statementnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_statement.html?$statementnum">!
+ : '';
+
+my $events = '';
+
+#if ( $cust_statement->num_cust_event
+# && ( $curuser->access_right('Billing event reports')
+# || $curuser->access_right('View customer billing events')
+# )
+# ) {
+# $events =
+# qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?statementnum=!.
+# $cust_statement->statementnum. '">(&nbsp;View statement events&nbsp;)</A></FONT>';
+#}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html
index 9cbc47b..6103727 100644
--- a/httemplate/view/cust_main/payment_history/voided_payment.html
+++ b/httemplate/view/cust_main/payment_history/voided_payment.html
@@ -8,15 +8,32 @@ my( $cust_pay_void, %opt ) = @_;
my $curuser = $FS::CurrentUser::CurrentUser;
my $payby = $cust_pay_void->payby;
-my $payinfo = $payby eq 'CARD'
- ? $cust_pay_void->paymask
- : $cust_pay_void->payinfo;
+
+my $payinfo;
+if ( $payby eq 'CARD' ) {
+ $payinfo = $cust_pay_void->paymask;
+} elsif ( $payby eq 'CHEK' ) {
+ my( $account, $aba ) = split('@', $cust_pay_void->paymask );
+ $payinfo = "ABA $aba, Acct #$account";
+} else {
+ $payinfo = $cust_pay_void->payinfo;
+}
$payby =~ s/^BILL$/Check #/ if $payinfo;
$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^PREP$/Prepaid card /;
+$payby =~ s/^CARD$/Credit card #/;
+$payby =~ s/^COMP$/Complimentary by /;
+$payby =~ s/^CASH$/Cash/;
+$payby =~ s/^WEST$/Western Union/;
+$payby =~ s/^MCRD$/Manual credit card/;
$payby =~ s/^BILL$//;
-$payby =~ s/^(CARD|COMP)$/$1 /;
-my $info = $payby ? " ($payby$payinfo)" : '';
+my $info = $payby ? "($payby$payinfo)" : '';
+
+if ( $opt{'pkg-balances'} && $cust_pay_void->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay_void->pkgnum } );
+ $info .= ' for '. $cust_pkg->pkg_label_long;
+}
my $unvoid = '';
if ( $cust_pay_void->closed !~ /^Y/i
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
index b5d581d..167849c 100644
--- a/httemplate/view/cust_main/tickets.html
+++ b/httemplate/view/cust_main/tickets.html
@@ -1,6 +1,3 @@
-<A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A>
-<BR>
-
(<A HREF="<% $open_link %>">View <% $openlabel %> tickets for this customer</A>)
(<A HREF="<% $res_link %>">View resolved tickets for this customer</A>)
<BR>
diff --git a/httemplate/view/cust_pay.html b/httemplate/view/cust_pay.html
index c36d769..2f23d9e 100644
--- a/httemplate/view/cust_pay.html
+++ b/httemplate/view/cust_pay.html
@@ -1,12 +1,12 @@
% if ( $link eq 'popup' ) {
- <% include('/elements/header-popup.html', "Payment Receipt" ) %>
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
<CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
% } elsif ( $link eq 'print' ) {
- <% include('/elements/header-popup.html', "Payment Receipt" ) %>
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
% #it would be nice if the menubar could be hidden for print, but better to
% # have it available than not, otherwise the user winds up at a dead end
@@ -18,7 +18,7 @@
% } else {
- <% include('/elements/header.html', "Payment Receipt", menubar(
+ <% include('/elements/header.html', "$thing Receipt", menubar(
"View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
'Print receipt' => $pr_link,
))
@@ -48,6 +48,20 @@
<TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->_date %></B></TD>
</TR>
+% if ( $void ) {
+
+ <TR>
+ <TD ALIGN="right">Void Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->void_date %></B></TD>
+ </TR>
+
+%# <TR>
+%# <TD ALIGN="right">Void reason</TD>
+%# <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->reason %></B></TD>
+%# </TR>
+
+% }
+
<TR>
<TD ALIGN="right">Amount</TD>
<TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_pay->paid %></B></TD>
@@ -79,6 +93,15 @@
% }
+% if ( $conf->exists('pkg-balances') && $cust_pay->pkgnum ) {
+% my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ <TR>
+ <TD ALIGN="right">For package</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pkg->pkg_label_long %></B></TD>
+ </TR>
+
+% }
+
</TABLE>
% if ( $link eq 'print' ) {
@@ -112,16 +135,20 @@ if ( $cgi->param('link') =~ /^(\w+)$/ ) {
$link = $1;
}
+my $void = $cgi->param('void') ? 1 : 0;
+my $thing = $void ? 'Voided Payment' : 'Payment';
+my $table = $void ? 'cust_pay_void' : 'cust_pay';
+
my $cust_pay = qsearchs({
- 'select' => 'cust_pay.*',
- 'table' => 'cust_pay',
+ 'select' => "$table.*",
+ 'table' => $table,
'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
'hashref' => { 'paynum' => $paynum },
'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
});
-die "Payment #$paynum not found!" unless $cust_pay;
+die "$thing #$paynum not found!" unless $cust_pay;
-my $pr_link = "${p}view/cust_pay.html?link=print;paynum=$paynum";
+my $pr_link = "${p}view/cust_pay.html?link=print;paynum=$paynum;void=$void";
my $custnum = $cust_pay->custnum;
my $display_custnum = $cust_pay->cust_main->display_custnum;
diff --git a/httemplate/view/cust_pay_void.html b/httemplate/view/cust_pay_void.html
new file mode 100644
index 0000000..8c22170
--- /dev/null
+++ b/httemplate/view/cust_pay_void.html
@@ -0,0 +1 @@
+<% include('cust_pay.html', @_, 'void' => 1 ) %>
diff --git a/httemplate/view/cust_statement-pdf.cgi b/httemplate/view/cust_statement-pdf.cgi
new file mode 100755
index 0000000..a1739e0
--- /dev/null
+++ b/httemplate/view/cust_statement-pdf.cgi
@@ -0,0 +1,28 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statementnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $pdf = $cust_statement->print_pdf( '', $templatename);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/cust_statement.html b/httemplate/view/cust_statement.html
new file mode 100755
index 0000000..3e1345e
--- /dev/null
+++ b/httemplate/view/cust_statement.html
@@ -0,0 +1,79 @@
+<% include("/elements/header.html",'Statement View', menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Resend invoices') ) {
+
+%# <A HREF="<% $p %>misc/send-statement.cgi?method=print;<% $link %>">Re-print this statement</A>
+
+% if ( grep { $_ ne 'POST' } $cust_statement->cust_main->invoicing_list ) {
+%# |
+ <A HREF="<% $p %>misc/send-statement.cgi?method=email;<% $link %>">Re-email this statement</A>
+% }
+
+% if ( 0 ) {
+% #if ( $conf->exists('hylafax') && length($cust_statement->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-statement.cgi?method=fax;<% $link %>">Re-fax this statement</A>
+% }
+
+ <BR><BR>
+
+% }
+
+
+% #if ( $conf->exists('invoice_latex') ) {
+% if ( 0 ) { #broken???
+
+ <A HREF="<% $p %>view/cust_statement-pdf.cgi?<% $link %>">View typeset statement</A>
+ <BR><BR>
+% }
+
+% #if ( $cust_statement->num_cust_event ) {
+% if ( 0 ) {
+<A HREF="<%$p%>search/cust_event.html?statementnum=<% $cust_statement->statementnum %>">(&nbsp;View statement events&nbsp;)</A><BR><BR>
+% }
+
+% if ( $conf->exists('invoice_html') ) {
+
+ <% join('', $cust_statement->print_html('', $templatename) ) %>
+% } else {
+
+ <PRE><% join('', $cust_statement->print_text('', $templatename) ) %></PRE>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statement
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $conf = new FS::Conf;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $custnum = $cust_statement->custnum;
+my $display_custnum = $cust_statement->cust_main->display_custnum;
+
+my $link = "statementnum=$statementnum";
+$link .= ';template='. uri_escape($templatename) if $templatename;
+
+</%init>
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index a0b4e37..852640e 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -1,30 +1,40 @@
-% # options example...
-% #
-% # 'table' => 'svc_something'
-% #
-% # 'labels' => {
-% # 'column' => 'Label',
-% # },
-% #
-% # listref - each item is a literal column name (or method) or (notyet) coderef
-% # if not specified all columns (except for the primary key) will be viewable
-% # 'fields' => [
-% # ]
-% #
-% # # defaults to "edit/$table.cgi?", will have svcnum appended
-% # 'edit_url' =>
-%
-%
-% if ( $custnum ) {
+<%doc>
+
+#Example:
+
+ include( 'elements/svc_Common.html,
+
+ 'table' => 'svc_something'
+ 'labels' => {
+ 'column' => 'Label',
+ },
+
+ #listref - each item is a literal column name (or method) or
+ # (notyet) coderef. if not specified all columns (except for the
+ #primary key) will be viewable
+ 'fields' => [
+ ]
+
+ # defaults to "edit/$table.cgi?", will have svcnum appended
+ 'edit_url' =>
+ )
+
+</%doc>
+% if ( $custnum ) {
<% include("/elements/header.html","View $label: $value") %>
<% include( '/elements/small_custview.html', $custnum, '', 1,
"${p}view/cust_main.cgi") %>
<BR>
+
% } else {
+ <% include("/elements/header.html","View $label: $value", menubar(
+ "Cancel this (unaudited) $label" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
+ )) %>
<SCRIPT>
function areyousure(href) {
@@ -33,13 +43,8 @@
}
</SCRIPT>
- <% include("/elements/header.html","View $label: $value", menubar(
- "Cancel this (unaudited) $label" =>
- "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
- )) %>
% }
-
Service #<B><% $svcnum %></B>
% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
@@ -130,7 +135,9 @@ my $svc_x = qsearchs({
' LEFT JOIN cust_pkg USING ( pkgnum ) '.
' LEFT JOIN cust_main USING ( custnum ) ',
'hashref' => { 'svcnum' => $svcnum },
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
}) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n";
my $cust_svc = $svc_x->cust_svc;
@@ -138,6 +145,14 @@ my($label, $value, $svcdb) = $cust_svc->label;
my $part_svc = $cust_svc->part_svc;
+ #false laziness w/edit/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ my $labels = $opt{labels}; #not -> here
+ foreach my $field ( keys %$labels ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\S*$/;
+ }
+
my $pkgnum = $cust_svc->pkgnum;
my($cust_pkg, $custnum);
diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html
index bb3a6dd..defbee9 100644
--- a/httemplate/view/svc_Common.html
+++ b/httemplate/view/svc_Common.html
@@ -1,3 +1,9 @@
+<% include('elements/svc_Common.html',
+ 'table' => $table,
+ 'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=",
+ %opt,
+ )
+%>
<%init>
# false laziness w/edit/svc_Common.html
@@ -21,9 +27,3 @@ if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
}
</%init>
-<% include('elements/svc_Common.html',
- 'table' => $table,
- 'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=",
- %opt,
- )
-%>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index e87a8ee..6a47ec7 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -214,7 +214,7 @@ Service #<B><% $svcnum %></B>
% if ($svc_acct->finger ne '') {
<TR>
- <TD ALIGN="right">GECOS</TD>
+ <TD ALIGN="right">Real Name</TD>
<TD BGCOLOR="#ffffff"><% $svc_acct->finger %></TD>
</TR>
% }
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
index 145d341..1463925 100644
--- a/httemplate/view/svc_broadband.cgi
+++ b/httemplate/view/svc_broadband.cgi
@@ -8,7 +8,9 @@
))
%>
-<A HREF="<%${p}%>edit/svc_broadband.cgi?<%$svcnum%>">Edit this information</A>
+<% include('/elements/init_overlib.html') %>
+
+<A HREF="<%$p%>edit/svc_broadband.cgi?<%$svcnum%>">Edit this information</A>
<BR>
<%ntable("#cccccc")%>
<TR>
@@ -22,10 +24,14 @@
<TD ALIGN="right">Description</TD>
<TD BGCOLOR="#ffffff"><%$description%></TD>
</TR>
- <TR>
- <TD ALIGN="right">Router</TD>
- <TD BGCOLOR="#ffffff"><%$routernum%>: <%$routername%></TD>
- </TR>
+
+% if ( $router ) {
+ <TR>
+ <TD ALIGN="right">Router</TD>
+ <TD BGCOLOR="#ffffff"><%$router->routernum%>: <%$router->routername%></TD>
+ </TR>
+% }
+
<TR>
<TD ALIGN="right">Download Speed</TD>
<TD BGCOLOR="#ffffff"><%$speed_down%></TD>
@@ -34,18 +40,25 @@
<TD ALIGN="right">Upload Speed</TD>
<TD BGCOLOR="#ffffff"><%$speed_up%></TD>
</TR>
- <TR>
- <TD ALIGN="right">IP Address</TD>
- <TD BGCOLOR="#ffffff"><%$ip_addr%></TD>
- </TR>
- <TR>
- <TD ALIGN="right">IP Netmask</TD>
- <TD BGCOLOR="#ffffff"><%$ip_netmask%></TD>
- </TR>
- <TR>
- <TD ALIGN="right">IP Gateway</TD>
- <TD BGCOLOR="#ffffff"><%$ip_gateway%></TD>
- </TR>
+
+% if ( $ip_addr ) {
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff">
+ <%$ip_addr%>
+ (<% include('/elements/popup_link-ping.html', 'ip'=>$ip_addr ) %>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Netmask</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->NetAddr->mask%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Gateway</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->ip_gateway%></TD>
+ </TR>
+% }
+
<TR>
<TD ALIGN="right">MAC Address</TD>
<TD BGCOLOR="#ffffff"><%$mac_addr%></TD>
@@ -173,18 +186,14 @@ if ($pkgnum) {
#eofalse
my $addr_block = $svc_broadband->addr_block;
-my $router = $addr_block->router;
+my $router = $addr_block->router if $addr_block;
-if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+#if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
my (
- $routername,
- $routernum,
$speed_down,
$speed_up,
$ip_addr,
- $ip_gateway,
- $ip_netmask,
$mac_addr,
$latitude,
$longitude,
@@ -193,13 +202,9 @@ my (
$auth_key,
$description,
) = (
- $router->getfield('routername'),
- $router->getfield('routernum'),
$svc_broadband->getfield('speed_down'),
$svc_broadband->getfield('speed_up'),
$svc_broadband->getfield('ip_addr'),
- $addr_block->ip_gateway,
- $addr_block->NetAddr->mask,
$svc_broadband->mac_addr,
$svc_broadband->latitude,
$svc_broadband->longitude,
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
index 8c1f4ce..fc099d8 100755
--- a/httemplate/view/svc_domain.cgi
+++ b/httemplate/view/svc_domain.cgi
@@ -7,9 +7,29 @@
)
)) %>
+<% include('/elements/error.html') %>
+
Service #<% $svcnum %>
<BR>Service: <B><% $part_svc->svc %></B>
<BR>Domain name: <B><% $domain %></B>
+% if ($export) {
+<BR>Status: <B><% $status %></B>
+% if ( $FS::CurrentUser::CurrentUser->access_right('Manage domain registration') ) {
+% if ( defined($ops{'register'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=register&svcnum=<% $svcnum %>">Register at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'transfer'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=transfer&svcnum=<% $svcnum %>">Transfer to <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'renew'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=renew&svcnum=<% $svcnum %>&period=1">Renew at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'revoke'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=revoke&svcnum=<% $svcnum %>">Revoke</A>
+% }
+% }
+% }
+
% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall') ) {
<BR>Catch all email <A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>:
% } else {
@@ -138,9 +158,9 @@ my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
my $pkgnum = $cust_svc->getfield('pkgnum');
my($cust_pkg, $custnum, $display_custnum);
if ($pkgnum) {
- $cust_pkg =qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $cust_pkg = qsearchs('cust_pkg', {'pkgnum'=>$pkgnum} );
$custnum = $cust_pkg->custnum;
- $custnum = $cust_pkg->cust_main->display_custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
} else {
$cust_pkg = '';
$custnum = '';
@@ -158,4 +178,37 @@ if ($svc_domain->catchall) {
my $domain = $svc_domain->domain;
+my $status = 'Unknown';
+my %ops = ();
+
+my @exports = $part_svc->part_export();
+
+my $registrar;
+my $export;
+
+# Find the first export that does domain registration
+foreach (@exports) {
+ $export = $_ if $_->can('registrar');
+}
+# If we have a domain registration export, get the registrar object
+if ($export) {
+ $registrar = $export->registrar;
+ my $domstat = $export->get_status( $svc_domain );
+ if (defined($domstat->{'message'})) {
+ $status = $domstat->{'message'};
+ } elsif (defined($domstat->{'unregistered'})) {
+ $status = 'Not registered';
+ $ops{'register'} = "Register";
+ } elsif (defined($domstat->{'status'})) {
+ $status = $domstat->{'status'} . ' ' . $domstat->{'contact_email'} . ' ' . $domstat->{'last_update_time'};
+ } elsif (defined($domstat->{'expdate'})) {
+ $status = "Expires " . $domstat->{'expdate'};
+ $ops{'renew'} = "Renew";
+ $ops{'revoke'} = "Revoke";
+ } else {
+ $status = $domstat->{'reason'};
+ $ops{'transfer'} = "Transfer";
+ }
+}
+
</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index f604daa..09d5be4 100644
--- a/httemplate/view/svc_phone.cgi
+++ b/httemplate/view/svc_phone.cgi
@@ -22,6 +22,74 @@
my $html_foot = sub {
my $svc_phone = shift;
+ ###
+ # Devices
+ ###
+
+ my $devices = '';
+
+ 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;
+ my $num_part_device = $sth->fetchrow_arrayref->[0];
+
+ my @phone_device = $svc_phone->phone_device;
+ if ( @phone_device || $num_part_device ) {
+ my $svcnum = $svc_phone->svcnum;
+ $devices .=
+ qq[Devices (<A HREF="${p}edit/phone_device.html?svcnum=$svcnum">Add device</A>)<BR>];
+ if ( @phone_device ) {
+
+ $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>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+ '</TR>';
+ my $bgcolor1 = '#eeeeee';
+ my $bgcolor2 = '#ffffff';
+ my $bgcolor = '';
+
+ foreach my $phone_device ( @phone_device ) {
+
+ if ( $bgcolor eq $bgcolor1 ) {
+ $bgcolor = $bgcolor2;
+ } else {
+ $bgcolor = $bgcolor1;
+ }
+ my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
+
+ my $devicenum = $phone_device->devicenum;
+
+ $devices .= '<TR>'.
+ $td. $phone_device->part_device->devicename. '</TD>'.
+ $td. $phone_device->mac_addr. '</TD>'.
+ "$td( ".
+ qq(<A HREF="${p}edit/phone_device.html?$devicenum">edit</A> | ).
+ qq(<A HREF="javascript:areyousure('${p}misc/delete-phone_device.html?$devicenum')">delete</A>).
+ ' )</TD>'.
+ '</TR>';
+ }
+ $devices .= '</TABLE><BR>';
+ }
+ $devices .= '<BR>';
+ }
+
+ ##
+ # CDR links
+ ##
+
tie my %what, 'Tie::IxHash',
'pending' => 'NULL',
'billed' => 'done',
@@ -43,9 +111,14 @@ my $html_foot = sub {
"View $_ CDRs</A>";
} keys(%what);
- my @ilinks = ( qq(<A HREF="${p}search/cdr.html?dst=$number">).
+ my @ilinks = ( qq(<A HREF="${p}search/cdr.html?cdrbatch=__ALL__;dst=$number">).
'View incoming CDRs</A>' );
+ ###
+ # concatenate & return
+ ###
+
+ $devices.
join(' | ', @links ). '<BR>'.
join(' | ', @ilinks). '<BR>';