summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2012-09-29 16:36:46 -0700
committerMark Wells <mark@freeside.biz>2012-09-29 16:36:46 -0700
commit33beebf4cb42eba3e1dd868ad5e0af102de961da (patch)
tree860712543dcc74ff2402a4ed8d73e8cd553e62d4 /httemplate
parent7ac86daf67b0a95153b736d5811f9050363f6553 (diff)
update address standardization for cust_location changes
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/agent.cgi28
-rw-r--r--httemplate/browse/cust_class.html7
-rw-r--r--httemplate/browse/cust_note_class.html2
-rw-r--r--httemplate/browse/ftp_target.html56
-rw-r--r--httemplate/browse/msg_template.html33
-rw-r--r--httemplate/browse/part_event.html2
-rwxr-xr-xhttemplate/browse/part_export.cgi7
-rwxr-xr-xhttemplate/browse/part_pkg.cgi2
-rwxr-xr-xhttemplate/browse/part_referral.html35
-rwxr-xr-xhttemplate/browse/part_svc.cgi11
-rw-r--r--httemplate/browse/part_svc_class.html34
-rw-r--r--httemplate/browse/payment_gateway.html4
-rw-r--r--httemplate/browse/radius_group.html19
-rw-r--r--httemplate/browse/reason.html17
-rwxr-xr-xhttemplate/browse/sales.cgi100
-rw-r--r--httemplate/browse/tower.html16
-rw-r--r--httemplate/config/config-process.cgi4
-rw-r--r--httemplate/config/config-view.cgi2
-rw-r--r--httemplate/config/config.cgi40
-rw-r--r--httemplate/docs/about.html6
-rw-r--r--httemplate/docs/license.html2
-rw-r--r--httemplate/docs/part_svc-table.html63
-rwxr-xr-xhttemplate/edit/agent.cgi46
-rw-r--r--httemplate/edit/cust_class.html23
-rwxr-xr-xhttemplate/edit/cust_main.cgi217
-rw-r--r--httemplate/edit/cust_main/after_bill_location.html12
-rw-r--r--httemplate/edit/cust_main/before_bill_location.html10
-rw-r--r--httemplate/edit/cust_main/billing.html95
-rw-r--r--httemplate/edit/cust_main/birthdate.html78
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js17
-rw-r--r--httemplate/edit/cust_main/company.html7
-rw-r--r--httemplate/edit/cust_main/fax.html5
-rw-r--r--httemplate/edit/cust_main/name.html53
-rw-r--r--httemplate/edit/cust_main/phones.html29
-rw-r--r--httemplate/edit/cust_main/stateid.html39
-rw-r--r--httemplate/edit/cust_main/top_misc.html27
-rwxr-xr-xhttemplate/edit/cust_pay.cgi10
-rwxr-xr-xhttemplate/edit/cust_refund.cgi2
-rw-r--r--httemplate/edit/discount.html14
-rw-r--r--httemplate/edit/elements/class_Common.html9
-rw-r--r--httemplate/edit/elements/edit.html22
-rw-r--r--httemplate/edit/elements/svc_Common.html14
-rwxr-xr-xhttemplate/edit/ftp_target.html46
-rw-r--r--httemplate/edit/invoice_template.html3
-rw-r--r--httemplate/edit/msg_template.html129
-rw-r--r--httemplate/edit/nas.html2
-rw-r--r--httemplate/edit/part_export.cgi77
-rwxr-xr-xhttemplate/edit/part_pkg.cgi4
-rwxr-xr-xhttemplate/edit/part_referral.html5
-rwxr-xr-xhttemplate/edit/part_svc.cgi138
-rw-r--r--httemplate/edit/part_svc_class.html6
-rw-r--r--httemplate/edit/payment_gateway.html58
-rw-r--r--httemplate/edit/pkg_class.html2
-rw-r--r--httemplate/edit/prepay_credit.cgi11
-rwxr-xr-xhttemplate/edit/process/agent.cgi41
-rw-r--r--httemplate/edit/process/cust_location.cgi6
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi61
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi8
-rw-r--r--httemplate/edit/process/cust_pkg_discount.html3
-rwxr-xr-xhttemplate/edit/process/cust_refund.cgi2
-rw-r--r--httemplate/edit/process/elements/process.html2
-rw-r--r--httemplate/edit/process/ftp_target.html12
-rw-r--r--httemplate/edit/process/msg_template.html4
-rw-r--r--httemplate/edit/process/part_export.cgi5
-rw-r--r--httemplate/edit/process/part_svc_class.html11
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi153
-rw-r--r--httemplate/edit/process/quotation.html11
-rw-r--r--httemplate/edit/process/sales.cgi23
-rwxr-xr-xhttemplate/edit/process/svc_acct.cgi27
-rw-r--r--httemplate/edit/process/svc_broadband.cgi7
-rw-r--r--httemplate/edit/prospect_main.html3
-rw-r--r--httemplate/edit/quick-charge.html106
-rw-r--r--httemplate/edit/quotation.html30
-rw-r--r--httemplate/edit/radius_group.html13
-rw-r--r--httemplate/edit/reason.html116
-rwxr-xr-xhttemplate/edit/sales.cgi79
-rwxr-xr-xhttemplate/edit/svc_acct.cgi6
-rw-r--r--httemplate/edit/svc_broadband.cgi16
-rw-r--r--httemplate/edit/svc_dsl.cgi24
-rw-r--r--httemplate/edit/tower.html4
-rw-r--r--httemplate/elements/coord-links.html12
-rw-r--r--httemplate/elements/customer-table.html105
-rw-r--r--httemplate/elements/dashboard-toplist.html103
-rw-r--r--httemplate/elements/form-create_ticket.html38
-rw-r--r--httemplate/elements/freeside.css20
-rw-r--r--httemplate/elements/header.html6
-rw-r--r--httemplate/elements/init_overlib.html11
-rw-r--r--httemplate/elements/location.html109
-rw-r--r--httemplate/elements/menu.html134
-rw-r--r--httemplate/elements/order_pkg_link.html26
-rw-r--r--httemplate/elements/popup_link-cust_main.html23
-rw-r--r--httemplate/elements/progress-init.html6
-rw-r--r--httemplate/elements/searchbar-ticket.html2
-rw-r--r--httemplate/elements/select-cust-part_pkg.html6
-rw-r--r--httemplate/elements/select-cust_pkg-status.html2
-rw-r--r--httemplate/elements/select-part_svc_class.html22
-rw-r--r--httemplate/elements/select-rt-customfield.html3
-rw-r--r--httemplate/elements/select-table.html46
-rw-r--r--httemplate/elements/standardize_locations.js5
-rw-r--r--httemplate/elements/table-tickets.html159
-rw-r--r--httemplate/elements/tr-amount_fee.html100
-rw-r--r--httemplate/elements/tr-cust_svc.html2
-rw-r--r--httemplate/elements/tr-fixed-cust_main.html15
-rw-r--r--httemplate/elements/tr-fixed-date.html13
-rw-r--r--httemplate/elements/tr-fixed-prospect_main.html15
-rw-r--r--httemplate/elements/tr-fixed.html16
-rw-r--r--httemplate/elements/tr-select-agent.html19
-rw-r--r--httemplate/elements/tr-select-cust-part_pkg.html27
-rw-r--r--httemplate/elements/tr-select-cust_location.html63
-rw-r--r--httemplate/elements/tr-select-discount.html26
-rw-r--r--httemplate/elements/tr-select-part_referral.html8
-rw-r--r--httemplate/elements/tr-select-part_svc_class.html27
-rwxr-xr-xhttemplate/elements/tr-select-reason.html57
-rw-r--r--httemplate/elements/tr-select-voip_class.html24
-rw-r--r--httemplate/elements/tr-svc_export_machine.html37
-rw-r--r--httemplate/elements/xmenu.css2
-rw-r--r--httemplate/elements/xmenu.top.css2
-rw-r--r--httemplate/graph/cust_bill_pkg.cgi140
-rw-r--r--httemplate/graph/elements/monthly.html38
-rw-r--r--httemplate/graph/elements/report.html31
-rw-r--r--httemplate/graph/money_time.cgi31
-rw-r--r--httemplate/graph/report_cust_bill_pkg.html59
-rw-r--r--httemplate/graph/report_money_time.html7
-rw-r--r--httemplate/index.html10
-rw-r--r--httemplate/loginout/logout.html4
-rw-r--r--httemplate/misc/batch-cust_pay.html357
-rwxr-xr-xhttemplate/misc/cancel_pkg.html93
-rw-r--r--httemplate/misc/cust-part_pkg.cgi18
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi2
-rw-r--r--httemplate/misc/cust_main-import.cgi10
-rwxr-xr-xhttemplate/misc/cust_main-suspend.cgi75
-rwxr-xr-xhttemplate/misc/cust_main-unsuspend.cgi56
-rw-r--r--httemplate/misc/delete-ftp_target.html18
-rw-r--r--httemplate/misc/download-batch.cgi13
-rw-r--r--httemplate/misc/order_pkg.html78
-rw-r--r--httemplate/misc/payment.cgi115
-rw-r--r--httemplate/misc/process/batch-cust_pay.cgi115
-rwxr-xr-xhttemplate/misc/process/cancel_pkg.html51
-rwxr-xr-xhttemplate/misc/process/void-cust_bill.html26
-rw-r--r--httemplate/misc/suspend_cust.html79
-rwxr-xr-xhttemplate/misc/timeworked.html2
-rw-r--r--httemplate/misc/unsuspend_cust.html68
-rwxr-xr-xhttemplate/misc/unvoid-cust_bill_void.html25
-rwxr-xr-xhttemplate/misc/unvoid-cust_pay_void.cgi2
-rw-r--r--httemplate/misc/void-cust_bill.html45
-rwxr-xr-xhttemplate/misc/void-cust_pay.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_bill-search.html18
-rw-r--r--httemplate/misc/xmlhttp-cust_main-search.cgi86
-rw-r--r--httemplate/pref/pref-process.html6
-rw-r--r--httemplate/pref/pref.html33
-rwxr-xr-xhttemplate/search/477.html54
-rwxr-xr-xhttemplate/search/477partIA_detail.html3
-rwxr-xr-xhttemplate/search/477partIA_summary.html3
-rwxr-xr-xhttemplate/search/477partIIA.html3
-rwxr-xr-xhttemplate/search/477partIIB.html194
-rwxr-xr-xhttemplate/search/477partV.html13
-rwxr-xr-xhttemplate/search/477partVI_census.html11
-rwxr-xr-xhttemplate/search/cust_bill.html4
-rw-r--r--httemplate/search/cust_bill_pay.html7
-rw-r--r--httemplate/search/cust_bill_pkg.cgi877
-rw-r--r--httemplate/search/cust_bill_pkg_referral.html304
-rwxr-xr-xhttemplate/search/cust_credit.html7
-rw-r--r--httemplate/search/cust_credit_bill.html7
-rw-r--r--httemplate/search/cust_credit_bill_pkg.html4
-rw-r--r--httemplate/search/cust_credit_refund.html7
-rw-r--r--httemplate/search/cust_main-zip.html48
-rwxr-xr-xhttemplate/search/cust_main.cgi35
-rwxr-xr-xhttemplate/search/cust_main.html23
-rwxr-xr-xhttemplate/search/cust_pay_pending.html1
-rw-r--r--httemplate/search/cust_pkg_summary.html2
-rw-r--r--httemplate/search/cust_svc.html123
-rw-r--r--httemplate/search/cust_tax_exempt_pkg.cgi8
-rw-r--r--httemplate/search/customer_accounting_summary.html14
-rw-r--r--httemplate/search/elements/cust_main_dayranges.html26
-rw-r--r--httemplate/search/elements/cust_pay_batch_top.html36
-rwxr-xr-xhttemplate/search/elements/cust_pay_or_refund.html16
-rw-r--r--httemplate/search/elements/search-csv.html13
-rw-r--r--httemplate/search/elements/search-html.html12
-rw-r--r--httemplate/search/elements/search-xls.html53
-rw-r--r--httemplate/search/elements/search.html6
-rwxr-xr-xhttemplate/search/pay_batch.cgi88
-rw-r--r--httemplate/search/prepaid_income.html (renamed from httemplate/search/report_prepaid_income.cgi)129
-rwxr-xr-xhttemplate/search/quotation.html268
-rwxr-xr-xhttemplate/search/report_477.html18
-rw-r--r--httemplate/search/report_cdr.html7
-rw-r--r--httemplate/search/report_cust_bill_pkg_referral.html66
-rw-r--r--httemplate/search/report_cust_main-zip.html4
-rwxr-xr-xhttemplate/search/report_cust_main.html69
-rwxr-xr-xhttemplate/search/report_customer_accounting_summary.html8
-rw-r--r--httemplate/search/report_prepaid_income.html19
-rw-r--r--httemplate/search/report_quotation.html75
-rwxr-xr-xhttemplate/search/report_receivables.html2
-rw-r--r--httemplate/search/report_rt_ticket.html1
-rw-r--r--httemplate/search/report_sqlradius_usage.html40
-rwxr-xr-xhttemplate/search/report_svc_acct.html2
-rwxr-xr-xhttemplate/search/report_svc_broadband.html2
-rwxr-xr-xhttemplate/search/report_svc_hardware.html2
-rwxr-xr-xhttemplate/search/report_tax-xls.cgi29
-rwxr-xr-xhttemplate/search/report_tax.cgi779
-rw-r--r--httemplate/search/sqlradius_usage.html201
-rw-r--r--httemplate/search/unearned_detail.html257
-rwxr-xr-xhttemplate/view/cust_bill.cgi2
-rwxr-xr-xhttemplate/view/cust_bill_void.html79
-rwxr-xr-xhttemplate/view/cust_main.cgi45
-rw-r--r--httemplate/view/cust_main/billing.html48
-rw-r--r--httemplate/view/cust_main/contacts.html153
-rw-r--r--httemplate/view/cust_main/custom_content.html31
-rw-r--r--httemplate/view/cust_main/custom_content/birthdate.html15
-rw-r--r--httemplate/view/cust_main/custom_content/small_custview.html4
-rw-r--r--httemplate/view/cust_main/custom_content/spouse_birthdate.html15
-rw-r--r--httemplate/view/cust_main/custom_content/svc_Common.html40
-rw-r--r--httemplate/view/cust_main/custom_content/svc_acct.html7
-rw-r--r--httemplate/view/cust_main/custom_content/svc_hardware.html7
-rw-r--r--httemplate/view/cust_main/custom_content/svc_phone.html7
-rwxr-xr-xhttemplate/view/cust_main/locations.html22
-rw-r--r--httemplate/view/cust_main/misc.html54
-rw-r--r--httemplate/view/cust_main/order_pkg_link.html22
-rwxr-xr-xhttemplate/view/cust_main/packages.html2
-rw-r--r--httemplate/view/cust_main/packages/services.html2
-rw-r--r--httemplate/view/cust_main/packages/status.html41
-rw-r--r--httemplate/view/cust_main/payment_history.html15
-rw-r--r--httemplate/view/cust_main/payment_history/invoice.html14
-rw-r--r--httemplate/view/cust_main/payment_history/payment.html2
-rw-r--r--httemplate/view/cust_main/payment_history/voided_invoice.html57
-rw-r--r--httemplate/view/cust_main/payment_history/voided_payment.html2
-rw-r--r--httemplate/view/cust_main/tickets.html166
-rwxr-xr-xhttemplate/view/cust_main_statement-pdf.cgi6
-rwxr-xr-xhttemplate/view/cust_msg.html2
-rw-r--r--httemplate/view/directions.html1
-rw-r--r--httemplate/view/elements/svc_Common.html4
-rw-r--r--httemplate/view/elements/tr-svc_export_machine.html27
-rw-r--r--httemplate/view/prospect_main.html38
-rw-r--r--httemplate/view/qual.cgi6
-rwxr-xr-xhttemplate/view/quotation.html91
-rwxr-xr-xhttemplate/view/svc_acct.cgi6
-rw-r--r--httemplate/view/svc_acct/basics.html8
-rwxr-xr-xhttemplate/view/svc_domain.cgi4
-rw-r--r--httemplate/view/svc_external.cgi5
-rwxr-xr-xhttemplate/view/svc_forward.cgi4
-rw-r--r--httemplate/view/svc_www.cgi4
240 files changed, 7737 insertions, 2778 deletions
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
index 64288b830..fc9ce5413 100755
--- a/httemplate/browse/agent.cgi
+++ b/httemplate/browse/agent.cgi
@@ -25,6 +25,7 @@ full offerings (via their type).<BR><BR>
<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Master Customer</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Commissions</TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice<BR>Template</FONT></TH>
<TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH>
@@ -93,6 +94,33 @@ full offerings (via their type).<BR><BR>
</TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+
+ <TABLE>
+
+% #surprising amount of false laziness w/ edit/process/agent.cgi
+% my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' });
+% foreach my $pkg_class ( '', @pkg_class ) {
+% my %agent_pkg_class = ( 'agentnum' => $agent->agentnum,
+% 'classnum' => $pkg_class ? $pkg_class->classnum : ''
+% );
+% my $agent_pkg_class =
+% qsearchs( 'agent_pkg_class', \%agent_pkg_class )
+% || new FS::agent_pkg_class \%agent_pkg_class;
+% my $param = 'classnum'. $agent_pkg_class{classnum};
+
+ <TR>
+ <TD><% $agent_pkg_class->commission_percent || 0 %>%</TD>
+ <TD><% $pkg_class ? $pkg_class->classname : mt('(no package class)') |h %>
+ </TD>
+ </TR>
+
+% }
+
+ </TABLE>
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
% foreach my $access_group (
% map $_->access_group,
% qsearch('access_groupagent', { 'agentnum' => $agent->agentnum })
diff --git a/httemplate/browse/cust_class.html b/httemplate/browse/cust_class.html
index d7c622837..70a279a05 100644
--- a/httemplate/browse/cust_class.html
+++ b/httemplate/browse/cust_class.html
@@ -42,4 +42,11 @@ if ($sth->fetchrow_arrayref->[0]) {
push @$links, $link;
}
+my $conf = new FS::Conf;
+if ( $conf->exists('cust_class-tax_exempt') ) {
+ push @$header, 'Tax exempt';
+ push @$fields, 'tax';
+ push @$links, '';
+}
+
</%init>
diff --git a/httemplate/browse/cust_note_class.html b/httemplate/browse/cust_note_class.html
index f5d450b9f..7928199b3 100644
--- a/httemplate/browse/cust_note_class.html
+++ b/httemplate/browse/cust_note_class.html
@@ -3,7 +3,7 @@
'html_init' => $html_init,
'name' => 'customer note classes',
'disableable' => 1,
- 'disabled_statuspos' => 2,
+ 'disabled_statuspos' => 1,
'query' => { 'table' => 'cust_note_class',
'hashref' => {},
'order_by' => 'ORDER BY classnum',
diff --git a/httemplate/browse/ftp_target.html b/httemplate/browse/ftp_target.html
new file mode 100644
index 000000000..4a5782058
--- /dev/null
+++ b/httemplate/browse/ftp_target.html
@@ -0,0 +1,56 @@
+<& elements/browse.html,
+ 'title' => 'FTP targets',
+ 'menubar' => [ 'Add a target' => $p.'edit/ftp_target.html', ],
+ 'name' => 'FTP targets',
+ 'query' => { 'table' => 'ftp_target',
+ 'hashref' => {},
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ 'Server',
+ 'Username',
+ 'Password',
+ 'Path',
+ 'Protocol',
+ '', #handling
+ ],
+ 'fields' => [ 'targetnum',
+ 'hostname',
+ 'username',
+ 'password',
+ 'path',
+ sub {
+ my $ftp_target = shift;
+ my $label;
+ if ($ftp_target->secure) {
+ $label = 'SFTP';
+ $label .= ' (port '.$ftp_target->port.')'
+ if $ftp_target->port != 22;
+ }
+ else {
+ $label = 'FTP';
+ $label .= ' (port '.$ftp_target->port.')'
+ if $ftp_target->port != 21;
+ }
+ $label;
+ },
+ 'handling',
+ ],
+ 'links' => [ $link, $link ],
+&>
+</TABLE>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+
+my $count_query = 'SELECT COUNT(*) FROM ftp_target';
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $link = [ $p.'edit/ftp_target.html?', 'targetnum' ];
+</%init>
diff --git a/httemplate/browse/msg_template.html b/httemplate/browse/msg_template.html
index 8a6ccf741..50afc283e 100644
--- a/httemplate/browse/msg_template.html
+++ b/httemplate/browse/msg_template.html
@@ -1,29 +1,32 @@
<% include( 'elements/browse.html',
'title' => 'Message templates',
'name_singular' => 'template',
- 'menubar' => [ 'Add a new template' =>
- $p.'edit/msg_template.html',
- ],
- 'query' => { 'table' => 'msg_template', },
- 'count_query' => 'SELECT COUNT(*) FROM msg_template',
- 'disableable' => 1,
+ 'menubar' => \@menubar,
+ 'query' => { 'table' => 'msg_template', },
+ 'count_query' => 'SELECT COUNT(*) FROM msg_template',
+ 'disableable' => 1,
'disabled_statuspos' => 2,
'agent_virt' => 1,
- 'agent_null_right' => ['Edit global templates','Configuration'],
+ 'agent_null_right' => ['View global templates','Edit global templates'],
'agent_pos' => 1,
- 'header' => [ 'Name', '', ('' x scalar @locales) ],
- 'fields' => [ 'msgname', @locales ],
- 'links' => [ $link, @locale_links ],
- 'cell_style' =>
- [ '', '', ($locale_style) x (scalar @locales) ],
+ 'header' => [ 'Name', '', map '', @locales ],
+ 'fields' => [ 'msgname', @locales ],
+ 'links' => [ $link, @locale_links ],
+ 'cell_style' => [ '', '', map $locale_style, @locales ],
)
%>
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit templates')
- || $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
- || $FS::CurrentUser::CurrentUser->access_right('Configuration');
+ unless $curuser->access_right([ 'View templates', 'View global templates',
+ 'Edit templates', 'Edit global templates', ]);
+
+my @menubar = ();
+if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) {
+ push @menubar, 'Add a new template' => $p.'edit/msg_template.html';
+}
my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ];
diff --git a/httemplate/browse/part_event.html b/httemplate/browse/part_event.html
index 03996435e..c06a14fe7 100644
--- a/httemplate/browse/part_event.html
+++ b/httemplate/browse/part_event.html
@@ -174,7 +174,7 @@ my $html_init =
qq!<A HREF="${p}edit/part_event.html"><I>Add a new event</I></A>!.
'&nbsp;or&nbsp;<SELECT NAME="clone"><OPTION></OPTION>';
-foreach my $part_event ( qsearch('part_event', {'diabled'=>''}) ) {
+foreach my $part_event ( qsearch('part_event', {'disabled'=>''}) ) {
$html_init .= '<OPTION VALUE="'. $part_event->eventpart. '">'.
$part_event->eventpart. ': '. $part_event->event. '</OPTION>';
}
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
index 8e28f4fc6..b7ecc00a6 100755
--- a/httemplate/browse/part_export.cgi
+++ b/httemplate/browse/part_export.cgi
@@ -36,10 +36,9 @@ function part_export_areyousure(href) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD>
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
-% if( $part_export->exportname ) {
- <B><% $part_export->exportname %>:</B><BR>
-% }
-<% $part_export->exporttype %> to <% $part_export->machine %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A>&nbsp;|&nbsp;<A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)</TD>
+ <% $part_export->label_html %>
+ (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A>&nbsp;|&nbsp;<A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)
+ </TD>
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
<% itable() %>
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 4ca78d718..e3d9de13a 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -436,7 +436,7 @@ push @fields,
[ map {
[
- { 'data' => $_,
+ { 'data' => "$_: ",
'align' => 'right',
},
{ 'data' => $part_pkg->format($_,$options{$_}),
diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html
index 9cc32c459..c7374673f 100755
--- a/httemplate/browse/part_referral.html
+++ b/httemplate/browse/part_referral.html
@@ -6,6 +6,13 @@ Where a customer heard about your service. Tracked for informational purposes.
<A HREF="<% $p %>edit/part_referral.html"><I>Add a new advertising source</I></A>
<BR><BR>
+<% $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled advertising sources</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled advertising sources</a> )'; }
+%>
+
<% include('/elements/table-grid.html') %>
% my $bgcolor1 = '#eeeeee';
% my $bgcolor2 = '#ffffff';
@@ -13,8 +20,12 @@ Where a customer heard about your service. Tracked for informational purposes.
<TR>
<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2 ROWSPAN=2>Advertising source</TH>
-% if ( $show_agentnums ) {
+% if ( ! $cgi->param('showdisabled') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc" ALIGN="center" ROWSPAN=2></TH>
+% }
+
+% if ( $show_agentnums ) {
<TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Agent</TH>
% }
@@ -27,7 +38,7 @@ Where a customer heard about your service. Tracked for informational purposes.
</TR>
-%foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) {
+%foreach my $part_referral ( FS::part_referral->all_part_referral(1,!scalar($cgi->param('showdisabled'))) ) {
%
% if ( $bgcolor eq $bgcolor1 ) {
% $bgcolor = $bgcolor2;
@@ -55,6 +66,16 @@ Where a customer heard about your service. Tracked for informational purposes.
% }
<% $part_referral->referral %><% $a ? '</A>' : '' %></TD>
+
+% if ( ! $cgi->param('showdisabled') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="center">
+ <% $part_referral->disabled
+ ? '<FONT COLOR="#FF0000"><B>DISABLED</B></FONT>'
+ : '<FONT COLOR="#00CC00"><B>Active</B></FONT>'
+ %>
+ </TD>
+% }
+
% if ( $show_agentnums ) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %></TD>
@@ -73,11 +94,11 @@ Where a customer heard about your service. Tracked for informational purposes.
<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
<TR>
<TD ALIGN="right"><B><% $num_cust %></B></TD>
- <TD ALIGN="left">customers</TD>
+ <TD ALIGN="left">&nbsp;customers&nbsp;</TD>
</TR>
<TR>
<TD ALIGN="right"><B><% $num_pkg %></B></TD>
- <TD ALIGN="left">packages</TD>
+ <TD ALIGN="left">&nbsp;packages&nbsp;</TD>
</TR>
</TABLE>
</TD>
@@ -94,7 +115,7 @@ Where a customer heard about your service. Tracked for informational purposes.
% or die dbh->errstr;
<TR>
- <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD>
+ <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=<% 2 + $show_agentnums + ! $cgi->param('showdisabled') %><B>Total</B></TD>
% for my $period ( keys %after ) {
% my @param = ( $today-$after{$period},
% $today+$before{$period},
@@ -108,11 +129,11 @@ Where a customer heard about your service. Tracked for informational purposes.
<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>
<TR>
<TD ALIGN="right"><B><% $num_cust %></B></TD>
- <TD ALIGN="left">customers</TD>
+ <TD ALIGN="left">&nbsp;customers&nbsp;</TD>
</TR>
<TR>
<TD ALIGN="right"><B><% $num_pkg %></B></TD>
- <TD ALIGN="left">packages</TD>
+ <TD ALIGN="left">&nbsp;packages&nbsp;</TD>
</TR>
</TABLE>
</TD>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index 26d090a3d..a8f4a7c84 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -141,16 +141,7 @@ function part_export_areyousure(href) {
%
<TR>
- <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">
-<% $part_export->exportnum %>:&nbsp;
-% if ($part_export->exportname) {
-<B><% $part_export->exportname %></B> (
-% }
-<% $part_export->exporttype %>&nbsp;to&nbsp;<% $part_export->machine %>
-% if ($part_export->exportname) {
-)
-% }
-</A></TD>
+ <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->label_html %></A></TD>
</TR>
% }
diff --git a/httemplate/browse/part_svc_class.html b/httemplate/browse/part_svc_class.html
new file mode 100644
index 000000000..73bd60305
--- /dev/null
+++ b/httemplate/browse/part_svc_class.html
@@ -0,0 +1,34 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Service classes',
+ 'html_init' => $html_init,
+ 'name' => 'service classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 1,
+ 'query' => { 'table' => 'part_svc_class',
+ 'hashref' => {},
+ 'order_by' => 'ORDER BY classnum',
+ },
+ 'count_query' => $count_query,
+ 'header' => $header,
+ 'fields' => $fields,
+ 'links' => $links,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Service classes are user-defined, informational types for services.<BR><BR>'.
+ qq!<A HREF="${p}edit/part_svc_class.html"><I>Add a service class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM part_svc_class';
+
+my $link = [ $p.'edit/part_svc_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links = [ $link, $link ];
+
+</%init>
diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html
index a06e5cf7c..7a8a668d7 100644
--- a/httemplate/browse/payment_gateway.html
+++ b/httemplate/browse/payment_gateway.html
@@ -77,9 +77,9 @@ my $options_sub = sub {
my $html = '<TABLE CELLSPACING=0 CELLPADDING=0>';
- my %options = $payment_gateway->options;
+ tie my %options, 'Tie::IxHash', $payment_gateway->options;
foreach my $option ( keys %options ) {
- $html .= '<TR><TH>'. $option. ':</TH>'.
+ $html .= '<TR><TH ALIGN="right">'. $option. ':</TH>'.
'<TD>'. $options{$option}. '</TD></TR>';
}
$html .= '</TABLE>';
diff --git a/httemplate/browse/radius_group.html b/httemplate/browse/radius_group.html
index fbf6d3766..98e81ab86 100644
--- a/httemplate/browse/radius_group.html
+++ b/httemplate/browse/radius_group.html
@@ -5,15 +5,26 @@
'query' => { 'table' => 'radius_group' },
'count_query' => 'SELECT COUNT(*) FROM radius_group',
'header' => [ '#', 'RADIUS Group', 'Description', 'Priority',
- 'Check', 'Reply' ],
+ 'Check', 'Reply', 'Speed' ],
'fields' => [ 'groupnum',
'groupname',
'description',
'priority',
- $check_attr, $reply_attr
+ $check_attr, $reply_attr,
+ sub {
+ my $group = shift;
+ if ($group->speed_down and $group->speed_up) {
+ return join (' / ', $group->speed_down, $group->speed_up);
+ } elsif ( $group->speed_down ) {
+ return $group->speed_down . ' down';
+ } elsif ( $group->speed_up ) {
+ return $group->speed_up . ' up';
+ }
+ '';
+ },
],
- 'align' => 'lllcll',
- 'links' => [ $link, $link, '', '', '', '',
+ 'align' => 'lllcllc',
+ 'links' => [ $link, $link, '', '', '', '', ''
],
&>
<%init>
diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html
index fe285be4a..14e97bf2f 100644
--- a/httemplate/browse/reason.html
+++ b/httemplate/browse/reason.html
@@ -17,14 +17,17 @@
'header' => [ '#',
ucfirst($classname) . ' Reason Type',
ucfirst($classname) . ' Reason',
+ ($class eq 'S' ? 'Unsuspension Fee' : ()),
],
'fields' => [ 'reasonnum',
sub { shift->reasontype->type },
'reason',
+ $unsuspend_pkg_comment,
],
'links' => [ $link,
$link,
'',
+ $unsuspend_pkg_link,
],
)
%>
@@ -50,4 +53,18 @@ my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' .
my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ];
+my ($unsuspend_pkg_comment, $unsuspend_pkg_link);
+if ( $class eq 'S' ) {
+ $unsuspend_pkg_comment = sub {
+ my $pkgpart = shift->unsuspend_pkgpart or return '';
+ my $part_pkg = FS::part_pkg->by_key($pkgpart) or return '';
+ $part_pkg->pkg_comment;
+ };
+
+ my $unsuspend_pkg_link = sub {
+ my $pkgpart = shift->unsuspend_pkgpart or return '';
+ [ $p."edit/part_pkg.cgi?", $pkgpart ];
+ };
+}
+
</%init>
diff --git a/httemplate/browse/sales.cgi b/httemplate/browse/sales.cgi
new file mode 100755
index 000000000..af098121d
--- /dev/null
+++ b/httemplate/browse/sales.cgi
@@ -0,0 +1,100 @@
+<% include("/elements/header.html",'Sales Listing', menubar(
+ 'Add new sales person' => '../edit/sales.cgi'
+)) %>
+Sales people bring in business.<BR><BR>
+% if ( dbdef->table('sales')->column('disabled') ) {
+
+ <% $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled sales people</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled sales people</a> )'; }
+ %>
+% }
+
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('sales')->column('disabled') ) ? 2 : 3 %>>Sales person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Agent</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH>
+</TR>
+
+%foreach my $sales ( sort {
+% $a->getfield('salesnum') cmp $b->getfield('salesnum')
+%} qsearch('sales', \%search ) ) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/sales.cgi?<% $sales->salesnum %>"><% $sales->salesnum %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/sales.cgi?<% $sales->salesnum %>"><% $sales->salesperson %></A>
+ </TD>
+
+% if ( ! $cgi->param('showdisabled') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="center">
+ <% $sales->disabled ? '<FONT COLOR="#FF0000"><B>DISABLED</B></FONT>'
+ : '<FONT COLOR="#00CC00"><B>Active</B></FONT>'
+ %>
+ </TD>
+% }
+
+% my ($agent) = qsearch('agent', { 'agentnum' => $sales->agentnum });
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/sales.cgi?<% $sales->agentnum %>"><% $sales->agentnum %></A>
+ <A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>">(<% $agent->agent %>)<BR>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% foreach my $access_group (
+% map $_->access_group,
+% qsearch('access_groupsales', { 'salesnum' => $sales->salesnum })
+% ) {
+ <A HREF="<%$p%>edit/access_group.html?<% $access_group->groupnum %>"><% $access_group->groupname |h %><BR>
+% }
+ </TD>
+
+ </TR>
+% }
+
+ </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>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %search;
+if ( $cgi->param('showdisabled')
+ || !dbdef->table('agent')->column('disabled') ) {
+ %search = ();
+} else {
+ %search = ( 'disabled' => '' );
+}
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/browse/tower.html b/httemplate/browse/tower.html
index 7767a3c36..7f096a748 100644
--- a/httemplate/browse/tower.html
+++ b/httemplate/browse/tower.html
@@ -8,9 +8,10 @@
'count_query' => 'SELECT COUNT(*) FROM tower',
'disableable' => 1,
'disabled_statuspos' => 1,
- 'header' => [ 'Name', 'Sectors', ],
+ 'header' => [ 'Name', 'Sectors', 'Coordinates'],
'fields' => [ $tower_sub,
$sector_sub,
+ $coord_sub,
],
'links' => [ ],
)
@@ -49,6 +50,19 @@ my $num_svc_links = sub {
},
};
+my $coord_sub = sub {
+ my $tower = shift;
+
+ my $coords = $m->scomp("/elements/coord-links.html", $tower->latitude, $tower->longitude, $tower->towername);
+
+ [
+ [
+ { 'data' => "Latitude: " . $tower->latitude . "<br>Longitude: " . $tower->longitude, },
+ { 'data' => $coords, 'link' => "Coordinates", },
+ ],
+ ]
+};
+
my $tower_sub = sub {
my $tower = shift;
my $sectors = join(',',
diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi
index 3dcb1d3ef..f1cbb1831 100644
--- a/httemplate/config/config-process.cgi
+++ b/httemplate/config/config-process.cgi
@@ -47,7 +47,7 @@
'</pre></font>';
% } elsif ( $type eq 'checkbox' ) {
-% if ( $conf->exists($i->key, $agentnum) ) {
+% if ( $conf->config_bool($i->key, $agentnum) ) {
configCell.style.backgroundColor = '#00ff00';
configCell.innerHTML = 'YES';
% } else {
@@ -184,7 +184,7 @@ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) {
}
# warn @touch;
$conf->touch($_, $agentnum) foreach @touch;
-$conf->delete($_, $agentnum) foreach @delete;
+$conf->delete_bool($_, $agentnum) foreach @delete;
if (scalar(@error)) {
$cgi->param('error', join(' ', @error));
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
index 02acd5853..02a24adbf 100644
--- a/httemplate/config/config-view.cgi
+++ b/httemplate/config/config-view.cgi
@@ -211,7 +211,7 @@ invoice language options:
% } elsif ( $type eq 'checkbox' ) {
<tr>
- <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
+ <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->config_bool($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
</tr>
% } elsif ( $type eq 'select' && $i->select_hash ) {
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
index 6a1eaecf7..7960d7e38 100644
--- a/httemplate/config/config.cgi
+++ b/httemplate/config/config.cgi
@@ -40,12 +40,14 @@ Setting <b><% $key %></b>
<table><tr><td>
% my $n = 0;
+% my $submit = 0;
% foreach my $type (@types) {
% if ( $type eq '' ) {
<font color="#ff0000">no type</font>
% } elsif ( $type eq 'image' ) {
+% $submit++;
<% $conf->exists($key, $agentnum)
? 'Current image<br>'.
@@ -59,24 +61,37 @@ Setting <b><% $key %></b>
New image filename <input type="file" name="<% "$key$n" %>">
% } elsif ( $type eq 'binary' ) {
+% $submit++;
Filename <input type="file" name="<% "$key$n" %>">
% } elsif ( $type eq 'textarea' ) {
+% $submit++;
<textarea name="<% "$key$n" %>" rows=12 cols=78 wrap="off"><% join("\n", $conf->config($key, $agentnum)) |h %></textarea>
% } elsif ( $type eq 'checkbox' ) {
+%
+% if ( $agentnum && $conf->exists($key) && ! $agent_bool ) {
- <input name="<% "$key$n" %>" type="checkbox" value="1"
- <% $conf->exists($key, $agentnum) ? 'CHECKED' : '' %> >
+ <input name="<% "$key$n" %>" type="checkbox" value="1" CHECKED DISABLED>
+ <FONT SIZE="-1"><I>(global setting cannot yet be overridden)</I></FONT>
-% } elsif ( $type eq 'text' ) {
+% } else {
+% $submit++;
- <input name="<% "$key$n" %>" type="text" value="<% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '' |h %>">
+ <input name="<% "$key$n" %>" type="checkbox" value="1"
+ <% $conf->config_bool($key, $agentnum) ? 'CHECKED' : '' %> >
+% }
-% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) {
+% } elsif ( $type eq 'text' ) {
+% $submit++;
+ <input name="<% "$key$n" %>" type="text" value="<% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '' |h %>">
+
+% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) {
+% $submit++;
+
<select name="<% "$key$n" %>" <% $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>>
%
@@ -131,7 +146,8 @@ Setting <b><% $key %></b>
</select>
-% } elsif ( $type eq 'select-sub' ) {
+% } elsif ( $type eq 'select-sub' ) {
+% $submit++;
<select name="<% "$key$n" %>" <% $config_item->multiple ? 'MULTIPLE' : '' %>>
@@ -167,8 +183,8 @@ Setting <b><% $key %></b>
</select>
-% } elsif ( $type eq 'editlist' ) {
-%
+% } elsif ( $type eq 'editlist' ) {
+% $submit++;
<script>
function doremove<% "$key$n" %>() {
fromObject = document.OneTrueForm.<% "$key$n" %>;
@@ -284,10 +300,10 @@ Setting <b><% $key %></b>
</tr></table>
% } elsif ( $element_types{$type} ) {
+% $submit++;
%
% my %opt = ( 'element_name' => "$key$n",
% 'empty_label' => ' ',
-% 'showdisabled' => 1,
% );
% if ( $config_item->multiple ) {
% $opt{'multiple'} = 1 if $config_item->multiple;
@@ -313,7 +329,10 @@ Setting <b><% $key %></b>
% }
</tr>
</table>
-<INPUT TYPE="submit" VALUE="<% $title %>">
+
+% if ( $submit ) {
+ <INPUT TYPE="submit" VALUE="<% $title %>">
+% }
</FORM>
</BODY>
@@ -365,5 +384,6 @@ my $config_item = $confitems{$key};
my $description = $config_item->description;
my $config_type = $config_item->type;
my @types = ref($config_type) ? @$config_type : ($config_type);
+my $agent_bool = $config_item->agent_bool;
</%init>
diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html
index e3bb28298..33b21a3ad 100644
--- a/httemplate/docs/about.html
+++ b/httemplate/docs/about.html
@@ -10,7 +10,7 @@
% my $url = $conf->config('company_url', $agentnum);
% if ( $url ) {
<BR><BR>
- <A HREF="<% $conf->config('company_url', $agentnum) %>" TARGET="_blank"><%$title%> homepage</A>
+ <FONT SIZE="+1"><A HREF="<% $conf->config('company_url', $agentnum) %>" TARGET="_top"><%$title%> homepage</A></FONT>
% }
% } else {
@@ -46,7 +46,7 @@ GNU <b>Affero</b> General Public License.<BR>
<BR><BR>
-<A HREF="http://www.freeside.biz/freeside" TARGET="_blank">Freeside homepage</A>
+<A HREF="http://www.freeside.biz/freeside" TARGET="_top">Freeside homepage</A>
% if ( $agentnum ) {
</FONT>
% }
@@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR>
% unless ( $agentnum ) {
<CENTER>
- <FONT SIZE="-3">"A selfish heart is trouble, but a foolish heart is worse" -R. Hunter</FONT>
+<!-- <FONT SIZE="-3">"" -R. Hunter</FONT> -->
</CENTER>
% }
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html
index fab8cd09f..e40b2436b 100644
--- a/httemplate/docs/license.html
+++ b/httemplate/docs/license.html
@@ -6,7 +6,7 @@
<P>
-Copyright &copy; 2005-2009 Freeside Internet Services, Inc.<BR>
+Copyright &copy; 2005-2012 Freeside Internet Services, Inc.<BR>
Copyright &copy; 2000-2005 Ivan Kohler<BR>
Copyright &copy; 1999 Silicon Interactive Software Design<BR>
All rights reserved<BR>
diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html
new file mode 100644
index 000000000..48841f550
--- /dev/null
+++ b/httemplate/docs/part_svc-table.html
@@ -0,0 +1,63 @@
+<& /elements/header-popup.html &>
+
+<TABLE>
+ <TR>
+ <TH ALIGN="left">Generic</TH>
+ <TH ALIGN="left">Access</TH>
+ <TH ALIGN="left">Telephony</TH>
+<!-- <TH>Hosting</TH>
+ <TH>Colocation</TH>
+-->
+ </TR>
+ <TR>
+ <TD VALIGN="top">
+ <UL STYLE="margin:0">
+ <LI><B>svc_acct</B>: Accounts - anything with a username (mailbox, shell, RADIUS, etc.)
+ <LI><B>svc_hardware</B>: Equipment supplied to customers
+ <LI><B>svc_external</B>: Externally-tracked service
+ </UL>
+ </TD>
+ <TD VALIGN="top">
+ <UL STYLE="margin:0">
+ <LI><B>svc_dsl</B>: DSL
+ <LI><B>svc_broadband</B>: Wireless broadband
+ <LI><B>svc_dish</B>: DISH Network
+ </UL>
+ </TD>
+ <TD VALIGN="top">
+ <UL STYLE="margin:0">
+ <LI><B>svc_phone</B>: Customer phone number
+ <LI><B>svc_pbx</B>: Customer PBX
+ </UL>
+ </TD>
+ </TR>
+</TABLE>
+<BR>
+<TABLE>
+ <TR>
+ <TH ALIGN="left">Hosting</TH>
+ <TH ALIGN="left">Colocation</TH>
+ </TR>
+ <TD VALIGN="top">
+ <UL STYLE="margin:0">
+ <LI><B>svc_domain</B>: Domain
+ <LI><B>svc_cert</B>: Certificate
+ <LI><B>svc_forward</B>: Mail forwarding
+ <LI><B>svc_mailinglist</B>: Mailing list
+ <LI><B>svc_www</B>: Virtual domain website
+ </UL>
+ </TD>
+ <TD VALIGN="top">
+ <UL STYLE="margin:0">
+ <LI><B>svc_port</B>: Customer router/switch port
+ </UL>
+ </TD>
+ </TR>
+<TABLE>
+<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
+ <LI>svc_wo - Work orders (Partially unimplemented)
+-->
+
+</BODY>
+</HTML>
+
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
index 6707d66a4..b043d1efe 100755
--- a/httemplate/edit/agent.cgi
+++ b/httemplate/edit/agent.cgi
@@ -19,9 +19,12 @@
</SCRIPT>
<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
-Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
-<% &ntable("#cccccc", 2, '') %>
+<FONT CLASS="fsinnerbox-title">
+ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
+</FONT>
+
+<TABLE CLASS="fsinnerbox">
<TR>
<TH ALIGN="right">Agent</TH>
@@ -117,8 +120,13 @@ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
</TR>
% }
+</TABLE>
+<BR>
+
+<FONT CLASS="fsinnerbox-title"><% mt('Access Groups') |h %></FONT>
+<TABLE CLASS="fsinnerbox">
+
<TR>
- <TD ALIGN="right">Access Groups</TD>
<TD><% include('/elements/checkboxes-table.html',
'source_obj' => $agent,
'link_table' => 'access_groupagent',
@@ -131,6 +139,38 @@ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
</TR>
</TABLE>
+<BR>
+
+<FONT CLASS="fsinnerbox-title"><% mt('Commissions') |h %></FONT>
+<TABLE CLASS="fsinnerbox">
+
+% #surprising amount of false laziness w/ edit/process/agent.cgi
+% my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' });
+% foreach my $pkg_class ( '', @pkg_class ) {
+% my %agent_pkg_class = ( 'agentnum' => $agent->agentnum,
+% 'classnum' => $pkg_class ? $pkg_class->classnum : ''
+% );
+% my $agent_pkg_class =
+% qsearchs( 'agent_pkg_class', \%agent_pkg_class )
+% || new FS::agent_pkg_class \%agent_pkg_class;
+% my $param = 'classnum'. $agent_pkg_class{classnum};
+
+ <TR>
+ <TD><INPUT TYPE = "text"
+ NAME = "<% $param %>"
+ VALUE = "<% $cgi->param($param) || $agent_pkg_class->commission_percent |h %>"
+ SIZE = 6
+ MAXLENGTH = 7
+ >%
+ </TD>
+ <TD><% $pkg_class ? $pkg_class->classname : mt('(no package class)') |h %>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+
<BR>
<INPUT TYPE="submit" VALUE="<% $agent->agentnum ? "Apply changes" : "Add agent" %>">
diff --git a/httemplate/edit/cust_class.html b/httemplate/edit/cust_class.html
index fdb58e687..8fce90588 100644
--- a/httemplate/edit/cust_class.html
+++ b/httemplate/edit/cust_class.html
@@ -1,5 +1,24 @@
<% include( 'elements/class_Common.html',
- 'name' => 'Customer Class',
- 'table' => 'cust_class',
+ 'name' => 'Customer Class',
+ 'table' => 'cust_class',
+ 'addl_fields' => \@addl_fields,
+ 'addl_labels' => { 'tax' => 'Tax Exempt' },
)
%>
+<%init>
+
+my $conf = new FS::Conf;
+
+my @addl_fields = ();
+if ( $conf->exists('cust_class-tax_exempt') ) {
+ push @addl_fields, { 'field' => 'tax',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ };
+} else {
+ push @addl_fields, { 'field' => 'tax',
+ 'type' => 'hidden',
+ };
+}
+
+</%init>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index ca9f8677b..f744884d7 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -23,120 +23,93 @@
% }
%# agent, agent_custid, refnum (advertising source), referral_custnum
+%# better section title for this?
+<FONT CLASS="fsinnerbox-title"><% mt('Basics') |h %></FONT>
<& cust_main/top_misc.html, $cust_main, 'custnum' => $custnum &>
%# birthdate
-% if ( $conf->exists('cust_main-enable_birthdate') ) {
+% if ( $conf->config('national_id-country')
+% || $conf->exists('cust_main-enable_birthdate')
+% || $conf->exists('cust_main-enable_spouse_birthdate')
+% || $conf->exists('cust_main-enable_anniversary_date')
+% )
+% {
<BR>
<& cust_main/birthdate.html, $cust_main &>
% }
-
-%# contact info
-
-% my $same_checked = '';
-% my $ship_disabled = '';
-% my @ship_style = ();
-% unless ( $cust_main->ship_last && $same ne 'Y' ) {
-% $same_checked = 'CHECKED';
-% $ship_disabled = 'DISABLED';
-% push @ship_style, 'background-color:#dddddd';
-% foreach (
-% qw( last first company address1 address2 city county state zip country
-% latitude longitude coord_auto
-% daytime night fax mobile )
-% ) {
-% $cust_main->set("ship_$_", $cust_main->get($_) );
-% }
-% }
-
+% my $has_ship_address = '';
+% if ( $cgi->param('error') ) {
+% $has_ship_address = !$same;
+% } elsif ( $cust_main->custnum ) {
+% $has_ship_address = $cust_main->has_ship_address;
+% }
<BR>
-<FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
-
-<& cust_main/contact.html,
- 'cust_main' => $cust_main,
- 'pre' => '',
- 'onchange' => 'bill_changed(this)',
- 'disabled' => '',
- 'ss' => $ss,
- 'stateid' => $stateid,
- 'same_checked' => $same_checked, #for address2 "Unit #" labeling
-&>
+<TABLE> <TR>
+ <TD STYLE="width:650px">
+%#; padding-right:2px; vertical-align:top">
+ <FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
+ <TABLE CLASS="fsinnerbox">
+ <& cust_main/before_bill_location.html, $cust_main &>
+ <& /elements/location.html,
+ object => $cust_main->bill_location,
+ prefix => 'bill_',
+ enable_coords => 1,
+ &>
+ <& cust_main/after_bill_location.html, $cust_main &>
+ </TABLE>
+ </TD>
+</TR>
+<TR><TD STYLE="height:40px"></TD></TR>
+<TR>
+ <TD STYLE="width:650px">
+%#; padding-left:2px; vertical-align:top">
+ <FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
+ <INPUT TYPE="checkbox"
+ NAME="same"
+ ID="same"
+ onclick="samechanged(this)"
+ onkeyup="samechanged(this)"
+ VALUE="Y"
+ <% $has_ship_address ? '' : 'CHECKED' %>
+ ><% mt('same as billing address') |h %>
+ <TABLE CLASS="fsinnerbox" ID="table_ship_location">
+ <& /elements/location.html,
+ object => $cust_main->ship_location,
+ prefix => 'ship_',
+ enable_censustract => 1,
+ enable_district => 1,
+ enable_coords => 1,
+ &>
+ </TABLE>
+ <TABLE CLASS="fsinnerbox" ID="table_ship_location_blank"
+ STYLE="display:none">
+ <TR><TD></TD></TR>
+ </TABLE>
+ </TD>
+</TR></TABLE>
<SCRIPT>
-function bill_changed(what) {
- if ( what.form.same.checked ) {
-% for (qw( last first company address1 address2 city zip latitude longitude coord_auto daytime night fax mobile )) {
- what.form.ship_<%$_%>.value = what.form.<%$_%>.value;
-% }
-
- what.form.ship_country.selectedIndex = what.form.country.selectedIndex;
-
- function fix_ship_city() {
- what.form.ship_city_select.selectedIndex = what.form.city_select.selectedIndex;
- what.form.ship_city.style.display = what.form.city.style.display;
- what.form.ship_city_select.style.display = what.form.city_select.style.display;
- }
-
- function fix_ship_county() {
- what.form.ship_county.selectedIndex = what.form.county.selectedIndex;
- ship_county_changed(what.form.ship_county, fix_ship_city );
- }
-
- function fix_ship_state() {
- what.form.ship_state.selectedIndex = what.form.state.selectedIndex;
- ship_state_changed(what.form.ship_state, fix_ship_county );
- }
-
- ship_country_changed(what.form.ship_country, fix_ship_state );
-
- }
-}
function samechanged(what) {
+%# not display = 'none', because we still want it to take up space
+%# document.getElementById('table_ship_location').style.visibility =
+%# what.checked ? 'hidden' : 'visible';
+ var t1 = document.getElementById('table_ship_location');
+ var t2 = document.getElementById('table_ship_location_blank');
if ( what.checked ) {
- bill_changed(what);
-
-% my @fields = qw( last first company address1 address2 city city_select county state zip country latitude longitude daytime night fax mobile );
-% for (@fields) {
- what.form.ship_<%$_%>.disabled = true;
- what.form.ship_<%$_%>.style.backgroundColor = '#dddddd';
-% }
-
-% if ( $conf->exists('cust_main-require_address2') ) {
- document.getElementById('address2_required').style.visibility = '';
- document.getElementById('address2_label').style.visibility = '';
- document.getElementById('ship_address2_required').style.visibility = 'hidden';
- document.getElementById('ship_address2_label').style.visibility = 'hidden';
-% }
-
- } else {
-
-% for (@fields) {
- what.form.ship_<%$_%>.disabled = false;
- what.form.ship_<%$_%>.style.backgroundColor = '#ffffff';
-% }
-
-% if ( $conf->exists('cust_main-require_address2') ) {
- document.getElementById('address2_required').style.visibility = 'hidden';
- document.getElementById('address2_label').style.visibility = 'hidden';
- document.getElementById('ship_address2_required').style.visibility = '';
- document.getElementById('ship_address2_label').style.visibility = '';
-% }
-
+ t2.style.width = t1.clientWidth + 'px';
+ t2.style.height = t1.clientHeight + 'px';
+ t1.style.display = 'none';
+ t2.style.display = '';
+ }
+ else {
+ t2.style.display = 'none';
+ t1.style.display = '';
}
}
+samechanged(document.getElementById('same'));
</SCRIPT>
<BR>
-<FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT>
-
-<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>><% mt('same as billing address') |h %>
-<& cust_main/contact.html,
- 'cust_main' => $cust_main,
- 'pre' => 'ship_',
- 'onchange' => '',
- 'disabled' => $ship_disabled,
- 'style' => \@ship_style
-&>
<& cust_main/contacts_new.html,
'cust_main' => $cust_main,
@@ -229,32 +202,56 @@ my $conf = new FS::Conf;
#get record
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 = ();
my %svc_dsl = ();
my $prospectnum = '';
my $locationnum = '';
+my $same = '';
+
if ( $cgi->param('error') ) {
+ $same = ($cgi->param('same') || '') eq 'Y';
+ # false laziness w/ edit/process/cust_main.cgi
+ my %locations;
+ for my $pre (qw(bill ship)) {
+ my %hash;
+ foreach ( FS::cust_main->location_fields ) {
+ $hash{$_} = scalar($cgi->param($pre.'_'.$_));
+ }
+ $hash{'custnum'} = $cgi->param('custnum');
+ $locations{$pre} = qsearchs('cust_location', \%hash)
+ || FS::cust_location->new( \%hash );
+ }
+ if ( $same ) {
+ $locations{ship} = $locations{bill};
+ }
+
$cust_main = new FS::cust_main ( {
- map { $_, scalar($cgi->param($_)) } fields('cust_main')
+ map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main')),
+ map { ( "ship_$_", '' ) } (FS::cust_main->location_fields)
} );
+ for my $pre (qw(bill ship)) {
+ $cust_main->set($pre.'_location', $locations{$pre});
+ $cust_main->set($pre.'_locationnum', $locations{$pre}->locationnum);
+ }
+
$custnum = $cust_main->custnum;
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
+ $cust_main->national_id( $cgi->param('national_id1') || $cgi->param('national_id2') );
+
$prospectnum = $cgi->param('prospectnum') || '';
$pkgpart_svcpart = $cgi->param('pkgpart_svcpart') || '';
@@ -296,7 +293,7 @@ if ( $cgi->param('error') ) {
$cust_main->paycvv($paycvv);
}
@invoicing_list = $cust_main->invoicing_list;
- $ss = $cust_main->masked('ss');
+ $ss = $conf->exists('unmask_ss') ? $cust_main->ss : $cust_main->masked('ss');
$stateid = $cust_main->masked('stateid');
$payinfo = $cust_main->paymask;
@@ -355,6 +352,20 @@ if ( $cgi->param('error') ) {
$svc_dsl{$_} = $qual->$_
foreach qw( phonenum vendor_qual_id );
}
+ else {
+ my $countrydefault = $conf->config('countrydefault') || 'US';
+ my $statedefault = $conf->config('statedefault') || 'CA';
+ $cust_main->set('bill_location',
+ FS::cust_location->new(
+ { country => $countrydefault, state => $statedefault }
+ )
+ );
+ $cust_main->set('ship_location',
+ FS::cust_location->new(
+ { country => $countrydefault, state => $statedefault }
+ )
+ );
+ }
if ( $cgi->param('lock_pkgpart') =~ /^(\d+)$/ ) {
my $pkgpart = $1;
@@ -367,7 +378,7 @@ if ( $cgi->param('error') ) {
}
my %keep = map { $_=>1 } qw( error tagnum lock_agentnum lock_pkgpart );
-$cgi->delete( grep !$keep{$_}, $cgi->param );
+$cgi->delete( grep { !$keep{$_} && $_ !~ /^tax_/ } $cgi->param );
my $title = $custnum ? 'Edit Customer' : 'Add Customer';
$title = mt($title);
diff --git a/httemplate/edit/cust_main/after_bill_location.html b/httemplate/edit/cust_main/after_bill_location.html
new file mode 100644
index 000000000..2f4c3b51c
--- /dev/null
+++ b/httemplate/edit/cust_main/after_bill_location.html
@@ -0,0 +1,12 @@
+% if ( ! $conf->exists('cust-edit-alt-field-order') ) {
+ <& phones.html, $cust_main &>
+ <& fax.html, $cust_main &>
+% } else {
+ <& fax.html, $cust_main &>
+ <& company.html, $cust_main &>
+% }
+<& stateid.html, $cust_main &>
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+</%init>
diff --git a/httemplate/edit/cust_main/before_bill_location.html b/httemplate/edit/cust_main/before_bill_location.html
new file mode 100644
index 000000000..973201ecb
--- /dev/null
+++ b/httemplate/edit/cust_main/before_bill_location.html
@@ -0,0 +1,10 @@
+<& name.html, $cust_main &>
+% if ( ! $conf->exists('cust-edit-alt-field-order') ) {
+ <& company.html, $cust_main &>
+% } else {
+ <& phones.html, $cust_main &>
+% }
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+</%init>
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
index 18c7ae9a6..2925ca87c 100644
--- a/httemplate/edit/cust_main/billing.html
+++ b/httemplate/edit/cust_main/billing.html
@@ -117,6 +117,15 @@
//why? select.selectedIndex = 0;
}
}
+
+ function tax_changed(what) {
+ var num = document.getElementById(what.id + '_num');
+ if ( what.checked ) {
+ num.disabled = false;
+ } else {
+ num.disabled = true;
+ }
+ }
</SCRIPT>
@@ -437,14 +446,28 @@
% 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<% @exempt_groups ? ' (all taxes)' : '' %></TD>
- </TR>
+% if ( $conf->exists('cust_class-tax_exempt')
+% || $conf->exists('tax-cust_exempt-groups-require_individual_nums')
+% )
+% {
+
+ <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax eq 'Y' ? 'Y' : '' %>">
+
+% } else {
+
+ <TR>
+ <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
+% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group);
+% #escape $exempt_group for NAME etc.
+% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group"));
<TR>
- <TD 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>
+ <TD>&nbsp;&nbsp;<INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD>
+ <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD>
</TR>
% }
@@ -458,7 +481,7 @@
? 'CHECKED'
: ''
- %>> <% mt('Postal mail invoice') |h %>
+ %>> <% mt('Postal mail invoices') |h %>
</TD>
</TR>
@@ -470,15 +493,27 @@
? 'CHECKED'
: ''
- %>> <% mt('Fax invoice') |h %>
+ %>> <% mt('Fax invoices') |h %>
</TD>
</TR>
% }
-% unless ( $conf->exists('cust-email-high-visibility')) {
<TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoice_email" VALUE="Y" <%
+
+ ( $cust_main->invoice_noemail eq 'Y' )
+ ? ''
+ : 'CHECKED'
+
+ %>> <% mt('Email invoices') |h %>
+
+ </TD>
+ </TR>
+
+% unless ( $conf->exists('cust-email-high-visibility')) {
+ <TR>
<TD ALIGN="right" WIDTH="200">
<% $conf->exists('cust_main-require_invoicing_list_email', $agentnum)
? $r : '' %>Email address(es)
@@ -487,6 +522,17 @@
</TR>
% }
+% if ( $conf->exists('cust_main-select-prorate_day') ) {
+ <TR>
+ <TD ALIGN="right" WIDTH="200"><% mt('Prorate day (1-28)') |h %> </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="prorate_day" VALUE="<% $cust_main->prorate_day %>" SIZE=3 MAXLENGTH=2>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="prorate_day" VALUE="<% $cust_main->prorate_day %>">
+% }
+
<TR>
<TD ALIGN="right" WIDTH="200"><% mt('Invoice terms') |h %> </TD>
<TD WIDTH="408">
@@ -562,20 +608,25 @@ function toggle(obj) {
%my @available_locales = $conf->config('available-locales');
%if ( scalar(@available_locales) ) {
-% push @available_locales, '';
-% my %locale_labels = map {
-% my %ll;
-% my %info = FS::Locales->locale_info($_);
-% $ll{$_} = $info{name} . " (" . $info{country} . ")";
-% %ll;
-% } FS::Locales->locales;
- <& /elements/tr-select.html,
- 'label' => emt('Invoicing locale'),
- 'field' => 'locale',
- 'options' => \@available_locales,
- 'labels' => \%locale_labels,
- 'curr_value' => $cust_main->locale,
- &>
+% push @available_locales, ''
+% unless $cust_main->locale && $conf->exists('cust_main-require_locale');
+% my %locale_labels = map {
+% my %ll;
+% my %info = FS::Locales->locale_info($_);
+% $ll{$_} = $info{name} . " (" . $info{country} . ")";
+% %ll;
+% } FS::Locales->locales;
+%
+% my $label = ( $conf->exists('cust_main-require_locale') ? $r : '' ).
+% emt('Invoicing locale');
+
+ <& /elements/tr-select.html,
+ 'label' => $label,
+ 'field' => 'locale',
+ 'options' => \@available_locales,
+ 'labels' => \%locale_labels,
+ 'curr_value' => $cust_main->locale,
+ &>
% }
</TABLE>
diff --git a/httemplate/edit/cust_main/birthdate.html b/httemplate/edit/cust_main/birthdate.html
index b4e78e3b9..e1adbd3bd 100644
--- a/httemplate/edit/cust_main/birthdate.html
+++ b/httemplate/edit/cust_main/birthdate.html
@@ -1,16 +1,80 @@
<% 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
- )
+
+% # maybe put after the contact names?
+
+% my $id_country = $conf->config('national_id-country');
+% if ( $id_country ) {
+% if ( $id_country eq 'MY' ) {
+% my($old, $nric) = ( '', '');
+% if ( $cust_main->national_id =~ /^\d{6}\-\d{2}\-\d{4}$/ ) {
+% $nric = $cust_main->national_id;
+% } else { # elsif ( $cust_main->national_id =~ /^\w\d{9}$/ ) {
+% $old = $cust_main->national_id;
+% #} else {
+% # warn "unknown national_id format";
+%# <INPUT TYPE="hidden" NAME="national_id0" VALUE="<% $cust_main->national_id |h %>">
+% }
+
+ <% include( '/elements/tr-input-text.html',
+ 'field' => 'national_id1',
+ 'value' => $nric,
+ 'label' => 'NRIC',
+ )
+ %>
+ <% include( '/elements/tr-input-text.html',
+ 'field' => 'national_id2',
+ 'value' => $old,
+ 'label' => 'Old IC/Passport',
+ )
+ %>
+
+% } else {
+% warn "unknown national_id-country $id_country";
+% }
+% }
+
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+ <% include( '/elements/tr-input-date-field.html', {
+ 'name' => 'birthdate',
+ 'value' => $cust_main->birthdate,
+ 'label' => 'Date of Birth',
+ 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ),
+ 'usedatetime' => 1,
+ 'noinit' => $noinit++,
+ })
%>
+% }
+
+% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) {
+ <% include( '/elements/tr-input-date-field.html', {
+ 'name' => 'spouse_birthdate',
+ 'value' => $cust_main->spouse_birthdate,
+ 'label' => 'Spouse Date of Birth',
+ 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ),
+ 'usedatetime' => 1,
+ 'noinit' => $noinit++,
+ })
+ %>
+% }
+
+% if ( $conf->exists('cust_main-enable_anniversary_date') ) {
+ <% include( '/elements/tr-input-date-field.html', {
+ 'name' => 'anniversary_date',
+ 'value' => $cust_main->anniversary_date,
+ 'label' => 'Anniversary Date',
+ 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ),
+ 'usedatetime' => 1,
+ 'noinit' => $noinit++,
+ })
+ %>
+% }
+
</TABLE>
<%init>
my( $cust_main, %opt ) = @_;
my $conf = new FS::Conf;
+my $noinit = 0;
+
</%init>
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 4e1108727..b64f6bdb2 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -68,7 +68,9 @@ function copy_payby_fields() {
}
<% include( '/elements/standardize_locations.js',
- 'callback' => 'submit_continue();'
+ 'callback' => 'submit_continue();',
+ 'main_prefix' => 'bill_',
+ 'no_company' => 1,
)
%>
@@ -94,14 +96,17 @@ function copyelement(from, to) {
//alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
}
+var prefix;
+
% # the value in 'censustract' is the confirmed censustract; if it's set,
% # do nothing here
function confirm_censustract() {
+ prefix = document.getElementById('same').checked ? 'bill_' : 'ship_';
var cf = document.CustomerForm;
- if ( cf.elements['censustract'].value == '' ) {
+ if ( cf.elements[prefix+'censustract'].value == '' ) {
var address_info = form_address_info();
- address_info['ship_latitude'] = cf.elements['ship_latitude'].value;
- address_info['ship_longitude'] = cf.elements['ship_longitude'].value;
+ address_info[prefix+'latitude'] = cf.elements[prefix+'latitude'].value;
+ address_info[prefix+'longitude'] = cf.elements[prefix+'longitude'].value;
OLpostAJAX(
'<%$p%>/misc/confirm-censustract.html',
'q=' + encodeURIComponent(JSON.stringify(address_info)),
@@ -118,8 +123,8 @@ function confirm_censustract() {
%# called from confirm-censustract.html
function set_censustract(tract, year) {
var cf = document.CustomerForm;
- cf.elements['censustract'].value = tract;
- cf.elements['censusyear'].value = year;
+ cf.elements[prefix+'censustract'].value = tract;
+ cf.elements[prefix+'censusyear'].value = year;
submit_continue();
}
diff --git a/httemplate/edit/cust_main/company.html b/httemplate/edit/cust_main/company.html
new file mode 100644
index 000000000..8a6ed0bbf
--- /dev/null
+++ b/httemplate/edit/cust_main/company.html
@@ -0,0 +1,7 @@
+% my $cust_main = shift;
+<TR ID="company_row" <% $cust_main->company ? '' : 'STYLE="display:none"' %>>
+ <TD ALIGN="right"><% mt('Company') |h %></TD>
+ <TD COLSPAN=6><INPUT TYPE="text" NAME="company" ID="company" SIZE=60
+ VALUE="<% $cust_main->company |h %>">
+ </TD>
+</TR>
diff --git a/httemplate/edit/cust_main/fax.html b/httemplate/edit/cust_main/fax.html
new file mode 100644
index 000000000..237d4be44
--- /dev/null
+++ b/httemplate/edit/cust_main/fax.html
@@ -0,0 +1,5 @@
+% my $cust_main = shift;
+<TR>
+ <TD ALIGN="right"><% mt('Fax') |h %></TD>
+ <TD><INPUT TYPE="text" NAME="fax" VALUE="<% $cust_main->fax %>" SIZE=18></TD>
+</TR>
diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html
new file mode 100644
index 000000000..2641ec930
--- /dev/null
+++ b/httemplate/edit/cust_main/name.html
@@ -0,0 +1,53 @@
+<%def .namepart>
+% my ($field, $value, $label, $extra) = @_;
+<TD>
+ <INPUT TYPE="text" NAME="<% $field %>" VALUE="<% $value |h %>" <%$extra%>>
+ <BR><FONT SIZE=-1><% mt($label) %></FONT>
+</TD>
+</%def>
+
+<TR>
+ <TH VALIGN="top" ALIGN="right"><%$r%><% mt('Contact name') |h %></TH>
+ <TD COLSPAN=6>
+ <TABLE CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <& .namepart, 'last', $cust_main->last, 'Last' &>
+ <TD VALIGN="top"> , </TD>
+ <& .namepart, 'first', $cust_main->first, 'First' &>
+% if ( $conf->exists('show_ss') ) {
+ <TD>&nbsp;</TD>
+ <& .namepart, 'ss', $ss, 'SS#', "SIZE=11" &>
+% } else {
+ <INPUT TYPE="hidden" NAME="ss" VALUE="<% $ss %>">
+% }
+ </TR>
+ </TABLE>
+ </TD>
+</TR>
+
+% if ( $conf->exists('cust-email-high-visibility') ) {
+<TR>
+ <TD ALIGN="right">
+ <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum)
+ ? $r
+ : '' %>Email address(es)
+ </TD>
+ <TD BGCOLOR="#FFFF00">
+ <INPUT TYPE="text" NAME="invoicing_list"
+ VALUE=<% $cust_main->invoicing_list_emailonly_scalar %>>
+ </TD>
+</TR>
+% }
+<%init>
+my $cust_main = shift;
+my $agentnum = $cust_main->agentnum if $cust_main->custnum;
+my $conf = FS::Conf->new;
+my $r = '<font color="#ff0000">*</font>&nbsp;';
+my $ss;
+
+if ( $cgi->param('error') or $conf->exists('unmask_ss') ) {
+ $ss = $cust_main->ss;
+} else {
+ $ss = $cust_main->masked('ss');
+}
+</%init>
diff --git a/httemplate/edit/cust_main/phones.html b/httemplate/edit/cust_main/phones.html
new file mode 100644
index 000000000..9b23e0716
--- /dev/null
+++ b/httemplate/edit/cust_main/phones.html
@@ -0,0 +1,29 @@
+<TR>
+ <TD VALIGN="top" ALIGN="right"><% mt('Phones') |h %></TD>
+ <TD COLSPAN=6>
+ <TABLE CELLSPACING=0 CELLPADDING=0>
+ <TR>
+% foreach my $phone (qw(daytime night mobile)) {
+ <TD>
+ <INPUT TYPE="text"
+ NAME="<% $phone %>"
+ VALUE="<% $cust_main->get($phone) %>"
+ SIZE=18
+ >
+ <BR><FONT SIZE=-1><% mt($phone_label{$phone}) |h %></FONT>
+ </TD>
+ <TD>&nbsp;</TD>
+% }
+ </TR>
+ </TABLE>
+ </TD>
+</TR>
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+my %phone_label = (
+ daytime => 'Day Phone',
+ night => 'Night Phone',
+ mobile => 'Mobile',
+);
+</%init>
diff --git a/httemplate/edit/cust_main/stateid.html b/httemplate/edit/cust_main/stateid.html
new file mode 100644
index 000000000..2655f5142
--- /dev/null
+++ b/httemplate/edit/cust_main/stateid.html
@@ -0,0 +1,39 @@
+% if ( $conf->exists('show_stateid') ) {
+<TR>
+ <TD ALIGN="right"><% $stateid_label %></TD>
+ <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $stateid %>" SIZE=12></TD>
+ <TD><& /elements/select-state.html,
+ state => $cust_main->stateid_state,
+ country => $cust_main->country, # how does this work on new customer?
+ prefix => 'stateid_',
+ disable_countyupdate => 1,
+ &></TD>
+</TR>
+% } else {
+<INPUT TYPE="hidden" NAME="stateid" VALUE="<% $stateid %>">
+<INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $cust_main->stateid_state %>">
+% }
+
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+my $stateid;
+if ( $cgi->param('error') ) {
+ $stateid = $cust_main->stateid;
+} elsif ( $cust_main->custnum ) {
+ $stateid = $cust_main->masked('stateid');
+} else {
+ $stateid = '';
+}
+$cust_main->set('stateid_state' => $cust_main->state)
+ unless $cust_main->stateid_state;
+
+my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
+ ? 'Driver&rsquo;s License'
+ : FS::Msgcat::_gettext('stateid') || 'Driver&rsquo;s License';
+
+my $stateid_state_label =
+ FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
+ ? 'Driver&rsquo;s License State'
+ : FS::Msgcat::_gettext('stateid') || 'Driver&rsquo;s License State';
+</%init>
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
index 7ba167b7f..7ce283c6c 100644
--- a/httemplate/edit/cust_main/top_misc.html
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -20,27 +20,16 @@
<% $cust_main->residential_commercial eq 'Commercial' ? 'CHECKED' : '' %>
></TD>
</TR>
-
<SCRIPT TYPE="text/javascript">
- function rescom_changed() {
- var f = document.CustomerForm;
-
- if ( f.residential_commercial_Residential.checked ) {
- document.getElementById('contacts_div').style.display = 'none';
- } else { // if ( f.residential_commercial_Commercial.checked ) {
- document.getElementById('contacts_div').style.display = '';
- }
-
- if ( f.residential_commercial_Residential.checked && ! f.company.value.length ) {
- document.getElementById('company_row').style.display = 'none'
- } else { // if ( f.residential_commercial_Commercial.checked ) {
+ function rescom_changed(what) {
+ if ( what.checked == (what.value == 'Commercial' ) ) {
document.getElementById('company_row').style.display = '';
- }
-
- if ( f.residential_commercial_Residential.checked && ! f.ship_company.value.length ) {
- document.getElementById('ship_company_row').style.display = 'none'
- } else { // if ( f.residential_commercial_Commercial.checked ) {
- document.getElementById('ship_company_row').style.display = '';
+ document.getElementById('contacts_div').style.display = '';
+ } else {
+ if ( document.getElementById('company').value.length == 0 ) {
+ document.getElementById('company_row').style.display = 'none';
+ }
+ document.getElementById('contacts_div').style.display = 'none';
}
}
</SCRIPT>
diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi
index 7a1bb00fa..d4414e44e 100755
--- a/httemplate/edit/cust_pay.cgi
+++ b/httemplate/edit/cust_pay.cgi
@@ -23,6 +23,7 @@
<% mt('Payment') |h %>
<% ntable("#cccccc", 2) %>
+% if ( $FS::CurrentUser::CurrentUser->access_right('Backdate payment') ) {
<TR>
<TD ALIGN="right"><% mt('Date') |h %></TD>
<TD COLSPAN=2>
@@ -39,6 +40,15 @@
align: "BR"
});
</SCRIPT>
+% }
+% else {
+<TR>
+ <TD ALIGN="right"><% mt('Date') |h %></TD>
+ <TD COLSPAN=2>
+ <% time2str($date_format.' %r',$_date) %>
+ </TD>
+</TR>
+% }
<TR>
<TD ALIGN="right"><% mt('Amount') |h %></TD>
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
index ba9304066..1ef69fdae 100755
--- a/httemplate/edit/cust_refund.cgi
+++ b/httemplate/edit/cust_refund.cgi
@@ -141,7 +141,7 @@ my $reason = $cgi->param('reason');
my $link = $cgi->param('popup') ? 'popup' : '';
my @rights = ();
-push @rights, 'Post refund' if $payby =~ /^(BILL|CASH)$/;
+push @rights, 'Post refund' if $payby =~ /^(BILL|CASH|MCRD)$/;
push @rights, 'Post check refund' if $payby eq 'BILL';
push @rights, 'Post cash refund ' if $payby eq 'CASH';
push @rights, 'Refund payment' if $payby =~ /^(CARD|CHEK)$/;
diff --git a/httemplate/edit/discount.html b/httemplate/edit/discount.html
index b195eb37b..9bcd1e724 100644
--- a/httemplate/edit/discount.html
+++ b/httemplate/edit/discount.html
@@ -22,6 +22,7 @@
postfix => '<BR><FONT SIZE="-1"><I>(blank for non-expiring discount)</I></FONT>',
},
{ field => 'setup', type => 'checkbox', value=>'Y', },
+ #{ field => 'linked', type => 'checkbox', value=>'Y', },
],
'labels' => {
'discountnum' => 'Discount #',
@@ -32,6 +33,7 @@
'percent' => 'Percentage&nbsp;',
'months' => 'Duration (months)',
'setup' => 'Apply to setup fees',
+ #'linked' => 'Apply to add-on packages',
},
'viewall_dir' => 'browse',
'new_callback' => $new_callback,
@@ -114,6 +116,10 @@ my $javascript = <<END;
document.getElementById('percent_label').style.visibility = 'hidden';
document.getElementById('percent_input0').style.display = 'none';
document.getElementById('percent_input0').style.visibility = 'hidden';
+// document.getElementById('linked_label').style.display = 'none';
+// document.getElementById('linked_label').style.visibility = 'hidden';
+// document.getElementById('linked').style.display = 'none';
+// document.getElementById('linked').style.visibility = 'hidden';
} else if ( _type == 'Amount' ) {
document.getElementById('amount_label').style.display = '';
document.getElementById('amount_label').style.visibility = '';
@@ -123,6 +129,10 @@ my $javascript = <<END;
document.getElementById('percent_label').style.visibility = 'hidden';
document.getElementById('percent_input0').style.display = 'none';
document.getElementById('percent_input0').style.visibility = 'hidden';
+// document.getElementById('linked_label').style.display = 'none';
+// document.getElementById('linked_label').style.visibility = 'hidden';
+// document.getElementById('linked').style.display = 'none';
+// document.getElementById('linked').style.visibility = 'hidden';
} else if ( _type == 'Percentage' ) {
document.getElementById('amount_label').style.display = 'none';
document.getElementById('amount_label').style.visibility = 'hidden';
@@ -132,6 +142,10 @@ my $javascript = <<END;
document.getElementById('percent_label').style.visibility = '';
document.getElementById('percent_input0').style.display = '';
document.getElementById('percent_input0').style.visibility = '';
+// document.getElementById('linked_label').style.display = '';
+// document.getElementById('linked_label').style.visibility = '';
+// document.getElementById('linked').style.display = '';
+// document.getElementById('linked').style.visibility = '';
}
}
diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html
index 69da4db31..0a0916ebc 100644
--- a/httemplate/edit/elements/class_Common.html
+++ b/httemplate/edit/elements/class_Common.html
@@ -22,21 +22,22 @@ my %opt = @_;
my $table = $opt{'table'};
my @category;
+my $category_table;
unless ( $opt{'nocat'} ) {
- ( my $category_table = $table ) =~ s/class/category/ or die;
+ ( $category_table = $table ) =~ s/class/category/ or die;
@category = qsearch($category_table, { 'disabled' => '' });
}
my $fields = [ 'classname',
(scalar(@category)
- ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' }
+ ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>$category_table, 'name_col'=>'categoryname' }
: { field=>'categorynum', type=>'hidden' }
),
{ field=>'disabled', type=>'checkbox', value=>'Y', },
];
-push @$fields, $opt{'addl_fields'} if $opt{'addl_fields'};
+push @$fields, @{ $opt{'addl_fields'} } if $opt{'addl_fields'};
my %addl_labels = ();
-%addl_labels = %{$opt{'addl_labels'}} if $opt{'addl_labels'};
+%addl_labels = %{ $opt{'addl_labels'} } if $opt{'addl_labels'};
</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
index 73faad4c0..a24f23805 100644
--- a/httemplate/edit/elements/edit.html
+++ b/httemplate/edit/elements/edit.html
@@ -292,6 +292,9 @@ Example:
% #& deprecated weird value hashref used only by reason.html
% 'value' => $f->{'value'},
%
+% #fixed
+% 'noescape' => $f->{'noescape'},
+%
% #select(-*)
% 'options' => $f->{'options'},
% 'labels' => $f->{'labels'},
@@ -308,6 +311,7 @@ Example:
%
% #umm. for select-agent_types at least
% 'disabled' => $f->{'disabled'},
+% 'fixed' => $f->{'fixed'},
%
% #any?
% 'colspan' => $f->{'colspan'},
@@ -317,7 +321,7 @@ Example:
% $include_common{$_} = $f->{$_} foreach grep exists($f->{$_}),
% qw( js_only html_only select_only layers_only cell_style ),#selectlayers,?
% qw( empty_label ), # select-*
-% qw( value_col ), # select-table
+% qw( value_col compare_sub ), # select-table
% qw( table name_col ), #(select,checkboxes)-table
% qw( target_table link_table ), #checkboxes-table
% qw( hashref agent_virt agent_null agent_null_right ),#*-table
@@ -751,13 +755,15 @@ Example:
<BR>
- <INPUT TYPE = "submit"
- ID = "submit"
- VALUE = "<% ( !$clone && $object->$pkey() )
- ? "Apply changes"
- : "Add ". ( $opt{'name'} || $opt{'name_singular'} )
- %>"
- >
+% unless ($opt{'no_submit'}) {
+ <INPUT TYPE = "submit"
+ ID = "submit"
+ VALUE = "<% ( !$clone && $object->$pkey() )
+ ? "Apply changes"
+ : "Add ". ($opt{'name'} || $opt{'name_singular'})
+ %>"
+ >
+% }
</FORM>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index 38716f06e..0d9d36c07 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -103,7 +103,15 @@
$f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum
if $object->svcnum;
$f->{'extra_sql'} .= ' ) ';
- $f->{'disable_empty'} = $object->svcnum ? 1 : 0,
+ $f->{'disable_empty'} = $object->svcnum ? 1 : 0;
+ if ( $f->{'field'} eq 'mac_addr' ) {
+ $f->{'compare_sub'} = sub {
+ my($a, $b) = @_;
+ $a =~ s/[-: ]//g;
+ $b =~ s/[-: ]//g;
+ lc($a) eq lc($b);
+ };
+ }
} elsif ( $flag eq 'H' ) {
$f->{'type'} = 'select-hardware_type';
$f->{'hashref'} = {
@@ -127,6 +135,10 @@
$object->set('custnum', $cust_pkg->custnum);
}
+ if ( my $cb = $opt{'svc_field_callback'} ) {
+ &{ $cb }( $cgi, $object, $f);
+ }
+
},
'html_init' => sub {
diff --git a/httemplate/edit/ftp_target.html b/httemplate/edit/ftp_target.html
new file mode 100755
index 000000000..aebf9aaed
--- /dev/null
+++ b/httemplate/edit/ftp_target.html
@@ -0,0 +1,46 @@
+<& elements/edit.html,
+ 'post_url' => popurl(1).'process/ftp_target.html',
+ 'name' => 'FTP target',
+ 'table' => 'ftp_target',
+ 'viewall_url' => "${p}browse/ftp_target.html",
+ 'labels' => { targetnum => 'Target',
+ hostname => 'Server',
+ username => 'Username',
+ password => 'Password',
+ path => 'Directory',
+ port => 'Port',
+ secure => 'Use SFTP',
+ handling => 'Special handling',
+ },
+ 'fields' => [
+ { field => 'hostname', size => 40 },
+ { field => 'port', size => 8 },
+ { field => 'secure', type => 'checkbox', value => 'Y' },
+ 'username',
+ 'password',
+ { field => 'path', size => 40 },
+ { field => 'handling',
+ type => 'select',
+ options => [ FS::ftp_target->handling_types ],
+ },
+ ],
+ 'menubar' => \@menubar,
+ 'edit_callback' => $edit_callback,
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Configuration');
+
+my @menubar = ('View all FTP targets' => $p.'browse/ftp_target.html');
+my $edit_callback = sub {
+ my ($cgi, $object) = @_;
+ if ( $object->targetnum ) {
+ push @menubar, 'Delete this target',
+ $p.'misc/delete-ftp_target.html?'.$object->targetnum;
+ }
+};
+
+</%init>
diff --git a/httemplate/edit/invoice_template.html b/httemplate/edit/invoice_template.html
index 9cec62c86..3553c617b 100644
--- a/httemplate/edit/invoice_template.html
+++ b/httemplate/edit/invoice_template.html
@@ -8,7 +8,8 @@
<FORM ACTION="process/invoice_template.html" METHOD="POST">
<INPUT TYPE="hidden" NAME="confname" VALUE="<% $confname %>">
-% if ( $type eq 'html' ) {
+% #if ( $type eq 'html' ) {
+% if ( 0 ) { #this seems to broken, using a text editor for everything for now
<% include('/elements/htmlarea.html',
'field' => 'value',
diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html
index 941554532..115032a07 100644
--- a/httemplate/edit/msg_template.html
+++ b/httemplate/edit/msg_template.html
@@ -1,14 +1,57 @@
-<% include( 'elements/edit.html',
- 'html_init' => '<TABLE id="outerTable"><TR><TD>',
- 'body_etc' => $body_etc,
- 'name_singular' => 'template',
- 'table' => 'msg_template',
- 'viewall_dir' => 'browse',
- 'agent_virt' => 1,
- 'agent_null' => 1,
- 'agent_null_right' => ['Edit global templates', 'Configuration'],
+<& elements/edit.html,
+ 'html_init' => '<TABLE id="outerTable"><TR><TD>',
+ 'body_etc' => $body_etc,
+ 'name_singular' => 'template',
+ 'table' => 'msg_template',
+ 'viewall_dir' => 'browse',
+ 'agent_virt' => 1,
+ 'agent_null' => 1,
+ 'agent_null_right' => [ 'View global templates', 'Edit global templates' ],
- 'fields' => [
+ 'fields' => \@fields,
+ 'labels' => {
+ 'msgnum' => 'Template',
+ 'agentnum' => 'Agent',
+ 'msgname' => 'Template name',
+ 'from_addr' => 'From: ',
+ 'bcc_addr' => 'Bcc: ',
+ 'locale' => 'Language',
+ 'subject' => 'Subject: ',
+ 'body' => 'Message body',
+ },
+ 'edit_callback' => \&edit_callback,
+ 'error_callback' => \&edit_callback,
+ 'html_bottom' => '</DIV>',
+ 'html_foot' => ( $no_submit ? '' : "</TD>$sidebar</TR></TABLE>" ),
+ 'no_submit' => $no_submit,
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right([ 'View templates', 'View global templates',
+ 'Edit templates', 'Edit global templates',
+ ]);
+
+my $body_etc = '';
+$body_etc = q!onload="document.getElementById('locale').onchange()"!
+ if $cgi->param('locale') eq 'new';
+
+my $msgnum = $cgi->param('msgnum');
+my $msg_template = $msgnum ? qsearchs('msg_template', {msgnum=>$msgnum}) : '';
+
+my $no_submit = 0;
+my @fields = ();
+if ( $curuser->access_right('Edit global templates')
+ || ( $curuser->access_right('Edit templates')
+ && $msg_template
+ && $msg_template->agentnum
+ && $curuser->agentnums_href->{$msg_template->agentnum}
+ )
+ )
+{
+ push @fields,
{ field => 'agentnum',
type => 'select-agent',
},
@@ -25,33 +68,32 @@
type => 'htmlarea',
width => 763
},
- ],
- 'labels' => {
- 'msgnum' => 'Template',
- 'agentnum' => 'Agent',
- 'msgname' => 'Template name',
- 'from_addr' => 'From: ',
- 'bcc_addr' => 'Bcc: ',
- 'locale' => 'Language',
- 'subject' => 'Subject: ',
- 'body' => 'Message body',
- },
- 'edit_callback' => \&edit_callback,
- 'error_callback' => \&edit_callback,
- 'html_bottom' => '</DIV>',
- 'html_foot' => "</TD>$sidebar</TR></TABLE>",
- )
- %>
-<%init>
+ ;
+} else { #readonly
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit templates')
- || $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
- || $FS::CurrentUser::CurrentUser->access_right('Configuration');
+ $no_submit = 1;
-my $body_etc = '';
-$body_etc = q!onload="document.getElementById('locale').onchange()"!
- if $cgi->param('locale') eq 'new';
+ push @fields,
+ { field => 'agentnum',
+ type => 'select-agent',
+ fixed => 1,
+ },
+ { field => 'msgname', type => 'fixed', },
+ { field => 'from_addr', type => 'fixed', },
+ { field => 'bcc_addr', type => 'fixed', },
+ { type => 'tablebreak-tabs',
+ include_opt_callback => \&menubar_opt_callback,
+ },
+ # template_content fields
+ { field => 'locale', type => 'hidden' },
+ { field => 'subject', type => 'fixed', },
+ { field => 'body',
+ type => 'fixed',
+ noescape => 1,
+ },
+ ;
+
+}
sub new_callback {
my ($cgi, $object, $fields_listref, $opt_hashref) = @_;
@@ -182,8 +224,18 @@ my %substitutions = (
'$country' => 'Country',
'$daytime' => 'Day phone',
'$night' => 'Night phone',
+ '$mobile' => 'Mobile phone',
'$fax' => 'Fax',
],
+ 'service' => [
+ '$ship_address1' => 'Address line 1',
+ '$ship_address2' => 'Address line 2',
+ '$ship_city' => 'City',
+ '$ship_county' => 'County',
+ '$ship_state' => 'State',
+ '$ship_zip' => 'Zip',
+ '$ship_country' => 'Country',
+ ],
'cust_bill' => [
'$invnum' => 'Invoice#',
],
@@ -238,15 +290,10 @@ my %substitutions = (
'$error' => 'Decline reason',
],
);
-my @c = @{ $substitutions{'contact'} };
-for (my $i=0; $i<scalar(@c); $i += 2) {
- $c[$i] =~ s/\$(.*)/\$ship_$1/;
-}
-$substitutions{'shipping'} = \@c;
tie my %sections, 'Tie::IxHash', (
'contact' => 'Name and contact info (billing)',
-'shipping' => 'Name and contact info (shipping)',
+'service' => 'Service address',
'cust_main' => 'Customer status and payment info',
'cust_pkg' => 'Package fields',
'cust_bill' => 'Invoice fields',
diff --git a/httemplate/edit/nas.html b/httemplate/edit/nas.html
index 2e66fc3be..8e6232cdb 100644
--- a/httemplate/edit/nas.html
+++ b/httemplate/edit/nas.html
@@ -49,7 +49,7 @@ sub html_bottom {
'link_table' => 'export_nas',
'target_table' => 'part_export',
'hashref' => { 'exporttype' =>
- { op => 'LIKE', value => '%sqlradius' }
+ { op => 'LIKE', value => '%sqlradius%' }
},
'name_callback' => sub { $_[0]->label },
'default' => 'yes',
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index 1450ac3b3..0407ee77b 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -13,12 +13,6 @@
</TD>
</TR>
<TR>
- <TD ALIGN="right">Export host</TD>
- <TD>
- <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>">
- </TD>
-</TR>
-<TR>
<TD ALIGN="right">Export</TD>
<TD><% $widget->html %>
@@ -63,7 +57,7 @@ my $widget = new HTML::Widgets::SelectLayers(
'options' => \%layers,
'form_name' => 'dummy',
'form_action' => 'process/part_export.cgi',
- 'form_text' => [qw( exportnum exportname machine )],
+ 'form_text' => [qw( exportnum exportname )],
# 'form_checkbox' => [qw()],
'html_between' => "</TD></TR></TABLE>\n",
'layer_callback' => sub {
@@ -71,9 +65,69 @@ my $widget = new HTML::Widgets::SelectLayers(
my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!.
ntable("#cccccc",2);
- $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'.
- $exports->{$layer}{notes}. '</TD></TR>'
- if $layer;
+ if ( $layer ) {
+ $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'.
+ $exports->{$layer}{notes}. '</TD></TR>';
+
+ if ( $exports->{$layer}{no_machine} ) {
+ $html .= '<INPUT TYPE="hidden" NAME="machine" VALUE="">'.
+ '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">';
+ } else {
+ $html .= '<TR><TD ALIGN="right">Hostname or IP</TD><TD>';
+ my $machine = $part_export->machine;
+ if ( $exports->{$layer}{svc_machine} ) {
+ my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' );
+ my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' );
+ my $part_export_machine = '';
+ if ( $cgi->param('svc_machine') eq 'Y'
+ || $machine eq '_SVC_MACHINE'
+ )
+ {
+ $Y_CHK = 'CHECKED';
+ $N_CHK = 'CHECKED';
+ $machine_DISABLED = 'DISABLED';
+ $pem_DISABLED = '';
+ $machine = '';
+ $part_export_machine =
+ $cgi->param('part_export_machine')
+ || join "\n",
+ map $_->machine,
+ grep ! $_->disabled,
+ $part_export->part_export_machine;
+ }
+ my $oc = qq(onChange="${layer}_svc_machine_changed(this)");
+ $html .= qq[
+ <INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc>
+ <INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED>
+ <BR>
+ <INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc>
+ Selected in each customer service from these choices
+ <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA>
+
+ <SCRIPT TYPE="text/javascript">
+ function ${layer}_svc_machine_changed (what) {
+ if ( what.checked ) {
+ var machine = document.getElementById("${layer}_machine");
+ var part_export_machine = document.getElementById("${layer}_part_export_machine");
+ if ( what.value == 'Y' ) {
+ machine.disabled = true;
+ part_export_machine.disabled = false;
+ } else if ( what.value == 'N' ) {
+ machine.disabled = false;
+ part_export_machine.disabled = true;
+ }
+ }
+ }
+ </SCRIPT>
+ ];
+ } else {
+ $html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">).
+ '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">';
+ }
+ $html .= "</TD></TR>";
+ }
+
+ }
foreach my $option ( keys %{$exports->{$layer}{options}} ) {
my $optinfo = $exports->{$layer}{options}{$option};
@@ -106,7 +160,8 @@ my $widget = new HTML::Widgets::SelectLayers(
}
if ( $type eq 'select' ) {
my $size = defined($optinfo->{size}) ? " SIZE=" . $optinfo->{size} : '';
- my $multi = defined($optinfo->{multi}) ? ' MULTIPLE' : '';
+ my $multi = ($optinfo->{multi} || $optinfo->{multiple})
+ ? ' MULTIPLE' : '';
$html .= qq!<SELECT NAME="$option"$multi$size>!;
my @values = split '\s+', $value if $multi;
my @options;
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index cd0731370..f3ad8f52d 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -55,6 +55,7 @@
'svc_dst_pkgpart' => 'Include services of package',
'report_option' => 'Report classes',
'fcc_ds0s' => 'Voice-grade equivalents',
+ 'fcc_voip_class' => 'Category',
},
'fields' => [
@@ -196,6 +197,9 @@
{ type => 'tablebreak-tr-title',
value => 'FCC Form 477 information',
},
+ { field=>'fcc_voip_class',
+ type=>'select-voip_class',
+ },
{ field=>'fcc_ds0s', type=>'text', size=>6 },
)
: ()
diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html
index daf8773f0..e9fd79452 100755
--- a/httemplate/edit/part_referral.html
+++ b/httemplate/edit/part_referral.html
@@ -3,9 +3,12 @@
'table' => 'part_referral',
'fields' => [ 'referral',
{ field=>'agentnum', type=>'select-agent', },
+ { field=>'disabled', type=>'checkbox', value=>'Y' } ,
],
- 'labels' => { 'referral' => 'Advertising source',
+ 'labels' => { 'refnum' => 'Ad Source',
+ 'referral' => 'Advertising source',
'agentnum' => 'Agent',
+ 'disabled' => 'Disabled',
},
'viewall_dir' => 'browse',
)
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index fae896154..007c24629 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -1,17 +1,27 @@
-<% include('/elements/header.html', "$action Service Definition",
+<& /elements/header.html, "$action Service Definition",
menubar('View all service definitions' => "${p}browse/part_svc.cgi"),
#" onLoad=\"visualize()\""
- )
-%>
+&>
+
+<& /elements/init_overlib.html &>
+
+<BR>
<FORM NAME="dummy">
- Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %>
-<BR><BR>
-Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR>
+<FONT CLASS="fsinnerbox-title">Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %></FONT>
+<TABLE CLASS="fsinnerbox">
+<TR>
+ <TD ALIGN="right">Service</TD>
+ <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD>
+<TR>
+
+<& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &>
-Self-service access:
-<SELECT NAME="selfservice_access">
+<TR>
+ <TD ALIGN="right">Self-service access</TD>
+ <TD>
+ <SELECT NAME="selfservice_access">
% tie my %selfservice_access, 'Tie::IxHash', #false laziness w/browse/part_svc
% '' => 'Yes',
% 'hidden' => 'Hidden',
@@ -22,12 +32,22 @@ Self-service access:
<% $_ eq $hashref->{'selfservice_access'} ? 'SELECTED' : '' %>
><% $selfservice_access{$_} %>
% }
-</SELECT><BR>
+ </SELECT>
+ </TD>
+</TR>
-<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>>&nbsp;Disable new orders<BR>
-<INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>>&nbsp;Preserve this service on package cancellation<BR>
+<TR>
+ <TD ALIGN="right">Disable new orders</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Preserve this service on package cancellation</TD>
+ <TD><INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>>&nbsp;</TD>
+</TR>
+
+</TABLE>
<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>">
@@ -76,6 +96,18 @@ Self-service access:
% ? ( $hashref->{svcdb} )
% : FS::part_svc->svc_tables();
%
+% my $help = '';
+% unless ( $hashref->{svcpart} ) {
+% $help = '&nbsp;'.
+% include('/elements/popup_link.html',
+% 'action' => $p.'docs/part_svc-table.html',
+% 'label' => 'help',
+% 'actionlabel' => 'Service table help',
+% 'width' => 763,
+% #'height' => 400,
+% );
+% }
+%
% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
% my $widget = new HTML::Widgets::SelectLayers(
% #'selected_layer' => $p_svcdb,
@@ -84,15 +116,16 @@ Self-service access:
% 'form_name' => 'dummy',
% #'form_action' => 'process/part_svc.cgi',
% 'form_action' => 'part_svc.cgi', #self
-% 'form_text' => [ qw( svc svcpart ) ],
-% 'form_select' => [ 'selfservice_access' ],
-% 'form_checkbox' => [ 'disabled', 'preserve' ],
+% 'form_elements' => [qw( svc svcpart classnum selfservice_access
+% disabled preserve
+% )],
+% 'html_between' => $help,
% 'layer_callback' => sub {
% my $layer = shift;
%
% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
%
-% $html .= $svcdb_info;
+% #$html .= $svcdb_info;
%
% my $columns = 3;
% my $count = 0;
@@ -111,12 +144,7 @@ Self-service access:
% && qsearchs( 'export_svc', {
% exportnum => $part_export->exportnum,
% svcpart => $clone || $part_svc->svcpart });
-% $html .= '>'.$part_export->exportnum. ': ';
-% $html .= $part_export->exportname . '<DIV ALIGN="right"><FONT SIZE=-1>'
-% if ( $part_export->exportname );
-% $html .= $part_export->exporttype. ' to '. $part_export->machine;
-% $html .= '</FONT></DIV>' if ( $part_export->exportname );
-% $html .= '</TD>';
+% $html .= '>'. $part_export->label_html. '</TD>';
% $count++;
% $html .= '</TR><TR>' unless $count % $columns;
% }
@@ -267,6 +295,7 @@ Self-service access:
%
% $html .= include('/elements/select-table.html',
% 'element_name' => "${layer}__${field}_classnum",
+% 'id' => "${layer}__${field}_classnum",
% 'element_etc' => ( $is_inv
% ? $disabled
% : $nodisplay
@@ -349,6 +378,7 @@ Self-service access:
% $html .= include('/elements/select-hardware_class.html',
% 'curr_value' => $value,
% 'element_name' => "${layer}__${field}_classnum",
+% 'id' => "${layer}__${field}_classnum",
% 'element_etc' => $flag ne 'H' && $nodisplay,
% 'empty_label' => 'Select hardware class',
% );
@@ -382,7 +412,8 @@ Self-service access:
%
% $html .= include('/elements/progress-init.html',
% $layer, #form name
-% [ qw(svc svcpart selfservice_access disabled preserve
+% [ qw(svc svcpart classnum selfservice_access
+% disabled preserve
% exportnum),
% @fields ],
% 'process/part_svc.cgi',
@@ -401,9 +432,8 @@ Self-service access:
%
% },
% );
-%
-%
+<BR>
Table <% $widget->html %>
<% include('/elements/footer.html') %>
@@ -451,66 +481,6 @@ my %communigate_fields = (
#'svc_cert' => { map { $_=>1 } qw( ) },
);
-my $svcdb_info = '
-<TABLE>
- <TR>
- <TH ALIGN="left">Generic</TH>
- <TH ALIGN="left">Access</TH>
- <TH ALIGN="left">Telephony</TH>
-<!-- <TH>Hosting</TH>
- <TH>Colocation</TH>
--->
- </TR>
- <TR>
- <TD VALIGN="top">
- <UL STYLE="margin:0">
- <LI><B>svc_acct</B>: Accounts - anything with a username (mailbox, shell, RADIUS, etc.)
- <LI><B>svc_hardware</B>: Equipment supplied to customers
- <LI><B>svc_external</B>: Externally-tracked service
- </UL>
- </TD>
- <TD VALIGN="top">
- <UL STYLE="margin:0">
- <LI><B>svc_dsl</B>: DSL
- <LI><B>svc_broadband</B>: Wireless broadband
- <LI><B>svc_dish</B>: DISH Network
- </UL>
- </TD>
- <TD VALIGN="top">
- <UL STYLE="margin:0">
- <LI><B>svc_phone</B>: Customer phone number
- <LI><B>svc_pbx</B>: Customer PBX
- </UL>
- </TD>
- </TR>
-</TABLE>
-<BR>
-<TABLE>
- <TR>
- <TH ALIGN="left">Hosting</TH>
- <TH ALIGN="left">Colocation</TH>
- </TR>
- <TD VALIGN="top">
- <UL STYLE="margin:0">
- <LI><B>svc_domain</B>: Domain
- <LI><B>svc_cert</B>: Certificate
- <LI><B>svc_forward</B>: Mail forwarding
- <LI><B>svc_mailinglist</B>: Mailing list
- <LI><B>svc_www</B>: Virtual domain website
- </UL>
- </TD>
- <TD VALIGN="top">
- <UL STYLE="margin:0">
- <LI><B>svc_port</B>: Customer router/switch port
- </UL>
- </TD>
- </TR>
-<TABLE>
-<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
- <LI>svc_wo - Work orders (Partially unimplemented)
--->
-';
-
my $mod_info = '
For the selected table, you can give fields default or fixed (unchangable)
values, or select an inventory class to manually or automatically fill in
diff --git a/httemplate/edit/part_svc_class.html b/httemplate/edit/part_svc_class.html
new file mode 100644
index 000000000..0d9a00727
--- /dev/null
+++ b/httemplate/edit/part_svc_class.html
@@ -0,0 +1,6 @@
+<% include( 'elements/class_Common.html',
+ 'name' => 'Service class',
+ 'table' => 'part_svc_class',
+ 'nocat' => 1,
+ )
+%>
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
index cfb86048c..dfe52f109 100644
--- a/httemplate/edit/payment_gateway.html
+++ b/httemplate/edit/payment_gateway.html
@@ -6,11 +6,12 @@
'field_callback' => $field_callback,
'labels' => {
'gatewaynum' => 'Gateway',
+ 'gateway_namespace' => 'Gateway type',
'gateway_module' => 'Gateway',
'gateway_username' => 'Username',
'gateway_password' => 'Password',
'gateway_action' => 'Action',
- 'gateway_options' => 'Options: (Name/Value pairs, one element per line)',
+ 'gateway_options' => 'Options (Name/Value pairs, <BR>one element per line)',
'gateway_callback_url' => 'Callback URL',
},
)
@@ -18,18 +19,17 @@
<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];
+ var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>;
+ function changeNamespace(what) {
+ var ns = what.value;
+ var select_module = document.getElementById('gateway_module');
+ select_module.options.length = 0;
+ for (var x in modulesForNamespace[ns]) {
+ var o = document.createElement('option');
+ o.value = o.text = modulesForNamespace[ns][x];
+ select_module.add(o, null);
+ }
}
-
</SCRIPT>
<%init>
@@ -88,7 +88,17 @@ my %modules = (
'VirtualNet' => 'Business::OnlinePayment',
'WesternACH' => 'Business::OnlinePayment',
'WorldPay' => 'Business::OnlinePayment',
-);
+
+ 'KeyBank' => 'Business::BatchPayment',
+ 'Paymentech' => 'Business::BatchPayment',
+ 'TD_EFT' => 'Business::BatchPayment',
+);
+
+my %modules_for_namespace;
+for (keys %modules) {
+ $modules_for_namespace{$modules{$_}} ||= [];
+ push @{ $modules_for_namespace{$modules{$_}} }, $_;
+}
my @actions = (
'Normal Authorization',
@@ -99,17 +109,23 @@ my @actions = (
my $fields = [
{
field => 'gateway_namespace',
- type => 'hidden',
- curr_value_callback => sub { my($cgi, $object, $fref) = @_;
- $modules{$object->gateway_module}
- || 'Business::OnlinePayment'
- },
+ type => 'select',
+ options => [ qw(
+ Business::OnlinePayment
+ Business::BatchPayment
+ Business::OnlineThirdPartyPayment
+ ) ],
+ labels => {
+ 'Business::OnlinePayment' => 'Direct',
+ 'Business::BatchPayment' => 'Batch',
+ 'Business::OnlineThirdPartyPayment' => 'Hosted',
+ },
+ onchange => 'changeNamespace',
},
{
field => 'gateway_module',
type => 'select',
options => [ sort { lc($a) cmp lc ($b) } keys %modules ],
- onchange => 'setNamespace',
},
'gateway_username',
'gateway_password',
@@ -126,6 +142,8 @@ my $fields = [
{
field => 'gateway_options',
type => 'textarea',
+ rows => '12',
+ cols => '40',
curr_value_callback => sub { my($cgi, $object, $fref) = @_;
join("\r", $object->options );
},
@@ -135,7 +153,7 @@ my $fields = [
my $field_callback = sub {
my ($cgi, $object, $field_hashref ) = @_;
if ($object->gatewaynum) {
- if ( $field_hashref->{field} eq 'gateway_module' ) {
+ if ( $field_hashref->{field} =~ /gateway_(module|namespace)/ ) {
$field_hashref->{type} = 'fixed';
}
}
diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html
index 1bc100e36..c4e3d8ac5 100644
--- a/httemplate/edit/pkg_class.html
+++ b/httemplate/edit/pkg_class.html
@@ -10,7 +10,7 @@ my $conf = new FS::Conf;
my %opt = ();
if($conf->exists('cust_main-require_censustract')) {
- $opt{'addl_fields'} = qw( fcc_ds0s );
+ $opt{'addl_fields'} = [ 'fcc_ds0s' ];
$opt{'addl_labels'} = { 'fcc_ds0s' => 'FCC form 477 voice-grade equivalents' };
}
</%init>
diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi
index f7a1b0801..3f0d6ba1d 100644
--- a/httemplate/edit/prepay_credit.cgi
+++ b/httemplate/edit/prepay_credit.cgi
@@ -18,13 +18,12 @@ prepaid cards of
characters each
-<BR>for <SELECT NAME="agentnum"><OPTION>(any agent)
-% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) {
+<BR>for
- <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %>
-% }
-
-</SELECT>
+<& /elements/select-agent.html,
+ 'empty_label' => '(any agent)',
+ 'curr_value' => $agentnum,
+&>
<TABLE>
<TR><TD>Value:
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
index e776d281c..034c4cc50 100755
--- a/httemplate/edit/process/agent.cgi
+++ b/httemplate/edit/process/agent.cgi
@@ -1,11 +1,12 @@
<% include( 'elements/process.html',
- 'table' => 'agent',
- 'viewall_dir' => 'browse',
- 'viewall_ext' => 'cgi',
- 'process_m2m' => { 'link_table' => 'access_groupagent',
- 'target_table' => 'access_group',
- },
- 'edit_ext' => 'cgi',
+ 'table' => 'agent',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'process_m2m' => { 'link_table' => 'access_groupagent',
+ 'target_table' => 'access_group',
+ },
+ 'edit_ext' => 'cgi',
+ 'noerror_callback' => $process_agent_pkg_class,
)
%>
<%init>
@@ -18,4 +19,30 @@ if ( FS::Conf->new->exists('disable_acl_changes') ) {
die "shouldn't be reached";
}
+my $process_agent_pkg_class = sub {
+ my( $cgi, $agent ) = @_;
+
+ #surprising amount of false laziness w/ edit/agent.cgi
+ my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' });
+ foreach my $pkg_class ( '', @pkg_class ) {
+ my %agent_pkg_class = ( 'agentnum' => $agent->agentnum,
+ 'classnum' => $pkg_class ? $pkg_class->classnum : ''
+ );
+ my $agent_pkg_class =
+ qsearchs( 'agent_pkg_class', \%agent_pkg_class )
+ || new FS::agent_pkg_class \%agent_pkg_class;
+
+ my $param = 'classnum'. $agent_pkg_class{classnum};
+
+ $agent_pkg_class->commission_percent( $cgi->param($param) );
+
+ my $method = $agent_pkg_class->agentpkgclassnum ? 'replace' : 'insert';
+
+ my $error = $agent_pkg_class->$method;
+ die $error if $error; #XXX push this down into agent.pm w/better/transactional error handling
+
+ }
+
+};
+
</%init>
diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi
index 790fc8ea4..b9f93db8b 100644
--- a/httemplate/edit/process/cust_location.cgi
+++ b/httemplate/edit/process/cust_location.cgi
@@ -28,10 +28,12 @@ my $cust_location = qsearchs({
});
die "unknown locationnum $locationnum" unless $cust_location;
-my $new = {
+my $new = FS::cust_location->new({
+ custnum => $cust_location->custnum,
+ prospectnum => $cust_location->prospectnum,
map { $_ => scalar($cgi->param($_)) }
qw( address1 address2 city county state zip country )
-};
+});
my $error = $cust_location->move_to($new);
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 44fbb4f10..31ec4ab12 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -57,17 +57,40 @@ push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
$cgi->param('invoicing_list', join(',', @invoicing_list) );
+# is this actually used? if so, we need to clone locations...
+# but I can't find anything that sets this parameter to a non-empty value
+$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/;
+my $duplicate_of = $1;
+
+my %locations;
+for my $pre (qw(bill ship)) {
+
+ my %hash;
+ foreach ( FS::cust_main->location_fields ) {
+ $hash{$_} = scalar($cgi->param($pre.'_'.$_));
+ }
+ $hash{'custnum'} = $cgi->param('custnum');
+ warn Dumper \%hash if $DEBUG;
+ # if we can qsearchs it, then it's unchanged, so use that
+ $locations{$pre} = qsearchs('cust_location', \%hash)
+ || FS::cust_location->new( \%hash );
+
+}
+
+if ( ($cgi->param('same') || '') eq 'Y' ) {
+ $locations{ship} = $locations{bill};
+}
#create new record object
+# but explicitly avoid setting ship_ fields
my $new = new FS::cust_main ( {
- map {
- $_, scalar($cgi->param($_))
- } fields('cust_main')
+ map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main')),
+ map { ( "ship_$_", '' ) } (FS::cust_main->location_fields)
} );
-$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/;
-my $duplicate_of = $1;
+$new->invoice_noemail( ($cgi->param('invoice_email') eq 'Y') ? '' : 'Y' );
+
if ( $duplicate_of ) {
# then negate all changes to the customer; the only change we should
# make is to order a package, if requested
@@ -76,11 +99,9 @@ if ( $duplicate_of ) {
or die "nonexistent existing customer (custnum $duplicate_of)";
}
-if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) {
- $new->setfield("ship_$_", '') foreach qw(
- last first company address1 address2 city county state zip
- country daytime night fax
- );
+for my $pre (qw(bill ship)) {
+ $new->set($pre.'_location', $locations{$pre});
+ $new->set($pre.'_locationnum', $locations{$pre}->locationnum);
}
if ( $cgi->param('no_credit_limit') ) {
@@ -89,9 +110,16 @@ if ( $cgi->param('no_credit_limit') ) {
$new->tagnum( [ $cgi->param('tagnum') ] );
-my %usedatetime = ( 'birthdate' => 1 );
+$error ||= $new->set_national_id_from_cgi( $cgi );
-foreach my $dfield (qw( birthdate signupdate )) {
+my %usedatetime = ( 'birthdate' => 1,
+ 'spouse_birthdate' => 1,
+ 'anniversary_date' => 1,
+ );
+
+foreach my $dfield (qw(
+ signupdate birthdate spouse_birthdate anniversary_date
+)) {
if ( $cgi->param($dfield) && $cgi->param($dfield) =~ /^([ 0-9\-\/]{0,10})$/) {
@@ -130,6 +158,7 @@ $new->setfield('paid', $cgi->param('paid') )
my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups;
+my %tax_exempt = map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt;
#perhaps this stuff should go to cust_main.pm
if ( $new->custnum eq '' or $duplicate_of ) {
@@ -237,7 +266,7 @@ if ( $new->custnum eq '' or $duplicate_of ) {
else {
# create the customer
$error ||= $new->insert( \%hash, \@invoicing_list,
- 'tax_exemption'=> \@tax_exempt,
+ 'tax_exemption'=> \%tax_exempt,
'prospectnum' => scalar($cgi->param('prospectnum')),
);
@@ -256,6 +285,7 @@ if ( $new->custnum eq '' or $duplicate_of ) {
my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );
$error ||= "Old record not found!" unless $old;
+
if ( length($old->paycvv) && $new->paycvv =~ /^\s*\*+\s*$/ ) {
$new->paycvv($old->paycvv);
}
@@ -294,8 +324,11 @@ if ( $new->custnum eq '' or $duplicate_of ) {
local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG;
local($FS::Record::DEBUG) = $DEBUG if $DEBUG;
+ local($Data::Dumper::Sortkeys) = 1;
+ warn Dumper({ new => $new, old => $old }) if $DEBUG;
+
$error ||= $new->replace( $old, \@invoicing_list,
- 'tax_exemption' => \@tax_exempt,
+ 'tax_exemption' => \%tax_exempt,
);
warn "$me returned from replace" if $DEBUG;
diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi
index 06f5e64d5..ce0ec3212 100755
--- a/httemplate/edit/process/cust_pay.cgi
+++ b/httemplate/edit/process/cust_pay.cgi
@@ -39,7 +39,13 @@ $cgi->param('link') =~ /^(custnum|invnum|popup)$/
my $field = my $link = $1;
$field = 'custnum' if $field eq 'popup';
-my $_date = parse_datetime($cgi->param('_date'));
+my $_date;
+if ( $FS::CurrentUser::CurrentUser->access_right('Backdate payment') ) {
+ $_date = parse_datetime($cgi->param('_date'));
+}
+else {
+ $_date = time;
+}
my $new = new FS::cust_pay ( {
$field => $linknum,
diff --git a/httemplate/edit/process/cust_pkg_discount.html b/httemplate/edit/process/cust_pkg_discount.html
index 6f97a791e..4a71f6975 100644
--- a/httemplate/edit/process/cust_pkg_discount.html
+++ b/httemplate/edit/process/cust_pkg_discount.html
@@ -39,7 +39,8 @@ my $cust_pkg_discount = new FS::cust_pkg_discount {
'amount' => scalar($cgi->param('discountnum_amount')),
'percent' => scalar($cgi->param('discountnum_percent')),
'months' => scalar($cgi->param('discountnum_months')),
- 'setup' => scalar($cgi->param('discountnum_setup')),
+ 'setup' => scalar($cgi->param('discountnum_setup')),
+ #'linked' => scalar($cgi->param('discountnum_linked')),
#'disabled' => $self->discountnum_disabled,
};
my $error = $cust_pkg_discount->insert;
diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi
index f4cce6535..bde40727a 100755
--- a/httemplate/edit/process/cust_refund.cgi
+++ b/httemplate/edit/process/cust_refund.cgi
@@ -31,7 +31,7 @@ my $link = $cgi->param('popup') ? 'popup' : '';
my $payby = $cgi->param('payby');
my @rights = ();
-push @rights, 'Post refund' if $payby =~ /^(BILL|CASH)$/;
+push @rights, 'Post refund' if $payby =~ /^(BILL|CASH|MCRD)$/;
push @rights, 'Post check refund' if $payby eq 'BILL';
push @rights, 'Post cash refund ' if $payby eq 'CASH';
push @rights, 'Refund payment' if $payby =~ /^(CARD|CHEK)$/;
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
index 12b3bd94b..2d39e9dce 100644
--- a/httemplate/edit/process/elements/process.html
+++ b/httemplate/edit/process/elements/process.html
@@ -250,8 +250,6 @@ foreach my $value ( @values ) {
}
- $error ||= $new->check;
-
my @args = ();
if ( !$error && $opt{'args_callback'} ) {
@args = &{ $opt{'args_callback'} }( $cgi, $new );
diff --git a/httemplate/edit/process/ftp_target.html b/httemplate/edit/process/ftp_target.html
new file mode 100644
index 000000000..35f56c490
--- /dev/null
+++ b/httemplate/edit/process/ftp_target.html
@@ -0,0 +1,12 @@
+<& elements/process.html,
+ 'table' => 'ftp_target',
+ 'viewall_dir' => 'browse',
+ 'agent_null' => 1,
+&>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/msg_template.html b/httemplate/edit/process/msg_template.html
index 47fe978a8..b19f5c542 100644
--- a/httemplate/edit/process/msg_template.html
+++ b/httemplate/edit/process/msg_template.html
@@ -9,9 +9,7 @@
%>
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Edit templates')
- || $FS::CurrentUser::CurrentUser->access_right('Edit global templates')
- || $FS::CurrentUser::CurrentUser->access_right('Configuration');
+ unless $FS::CurrentUser::CurrentUser->access_right(['Edit templates','Edit global templates']);
sub precheck_callback {
my $cgi = shift;
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
index 21150ef67..6432d6b15 100644
--- a/httemplate/edit/process/part_export.cgi
+++ b/httemplate/edit/process/part_export.cgi
@@ -28,6 +28,11 @@ my $new = new FS::part_export ( {
} fields('part_export')
} );
+if ( $cgi->param('svc_machine') eq 'Y' ) {
+ $new->machine('_SVC_MACHINE');
+ $new->part_export_machine_textarea( $cgi->param('part_export_machine') );
+}
+
my $error;
if ( $exportnum ) {
#warn $old;
diff --git a/httemplate/edit/process/part_svc_class.html b/httemplate/edit/process/part_svc_class.html
new file mode 100644
index 000000000..16165dd18
--- /dev/null
+++ b/httemplate/edit/process/part_svc_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_svc_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
index fab85252b..c5eee0cb8 100644
--- a/httemplate/edit/process/quick-cust_pkg.cgi
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -2,19 +2,24 @@
% $cgi->param('error', $error);
<% $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';
-% my $redir_url = popurl(3)
-% ."view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag";
+%
+% my $redir_url = popurl(3);
+% if ( $svcpart ) { # for going straight to service provisining after ordering
+% $redir_url .= 'edit/'.$part_svc->svcdb.'.cgi?'.
+% 'pkgnum='.$cust_pkg->pkgnum. ";svcpart=$svcpart";
+% $redir_url .= ";qualnum=$qualnum" if $qualnum;
+% } elsif ( $quotationnum ) {
+% $redir_url .= "view/quotation.html?quotationnum=$quotationnum";
+% } else {
+% my $custnum = $cust_main->custnum;
+% my $frag = "cust_pkg". $cust_pkg->pkgnum;
+% $redir_url .=
+% "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag";
+% }
%
-% # for going right to a provision service after ordering a package
-% if ( $svcpart ) {
-% $redir_url = popurl(3)."edit/".$part_svc->svcdb.".cgi?".
-% "pkgnum=".$cust_pkg->pkgnum. ";svcpart=$svcpart";
-% $redir_url .= ";qualnum=$qualnum" if $qualnum;
-% }
<% header('Package ordered') %>
<SCRIPT TYPE="text/javascript">
// XXX fancy ajax rebuild table at some point, but a page reload will do for now
@@ -33,21 +38,35 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right('Order customer package');
-#untaint custnum (probably not necessary, searching for it is escape enough)
-$cgi->param('custnum') =~ /^(\d+)$/
- or die 'illegal custnum '. $cgi->param('custnum');
-my $custnum = $1;
-my $cust_main = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $custnum },
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-});
-die 'unknown custnum' unless $cust_main;
+my $cust_main;
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ my $custnum = $1;
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+}
+
+my $prospect_main;
+if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ my $prospectnum = $1;
+ $prospect_main = qsearchs({
+ 'table' => 'prospect_main',
+ 'hashref' => { 'prospectnum' => $prospectnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+}
+
+die 'no custnum or prospectnum' unless $cust_main || $prospect_main;
#probably not necessary, taken care of by cust_pkg::check
$cgi->param('pkgpart') =~ /^(\d+)$/
or die 'illegal pkgpart '. $cgi->param('pkgpart');
my $pkgpart = $1;
+$cgi->param('quantity') =~ /^(\d+)$/
+ or die 'illegal quantity '. $cgi->param('quantity');
+my $quantity = $1;
$cgi->param('refnum') =~ /^(\d*)$/
or die 'illegal refnum '. $cgi->param('refnum');
my $refnum = $1;
@@ -69,46 +88,70 @@ if ( $cgi->param('svcpart') ) {
}
my $qualnum = '';
-if ( $cgi->param('qualnum') ) {
- $cgi->param('qualnum') =~ /^(\d+)$/ or die 'illegal qualnum';
+if ( $cgi->param('qualnum') =~ /^(\d+)$/ ) {
$qualnum = $1;
}
+my $quotationnum = '';
+if ( $cgi->param('quotationnum') =~ /^(\d+)$/ ) {
+ $quotationnum = $1;
+}
+# verify this quotation is visible to this user
+my $cust_pkg = '';
+my $quotation_pkg = '';
+my $error = '';
-my $cust_pkg = new FS::cust_pkg {
- 'custnum' => $custnum,
- 'pkgpart' => $pkgpart,
- 'start_date' => ( scalar($cgi->param('start_date'))
- ? parse_datetime($cgi->param('start_date'))
- : ''
- ),
- 'no_auto' => scalar($cgi->param('no_auto')),
- 'refnum' => $refnum,
- 'locationnum' => $locationnum,
- 'discountnum' => $discountnum,
- #for the create a new discount case
- 'discountnum__type' => scalar($cgi->param('discountnum__type')),
- 'discountnum_amount' => scalar($cgi->param('discountnum_amount')),
- 'discountnum_percent' => scalar($cgi->param('discountnum_percent')),
- 'discountnum_months' => scalar($cgi->param('discountnum_months')),
- 'discountnum_setup' => scalar($cgi->param('discountnum_setup')),
- 'contract_end' => ( scalar($cgi->param('contract_end'))
- ? parse_datetime($cgi->param('contract_end'))
- : ''
- ),
- 'waive_setup' => ( $cgi->param('waive_setup') eq 'Y' ? 'Y' : '' ),
-};
-
-my %opt = ( 'cust_pkg' => $cust_pkg );
-
-if ( $locationnum == -1 ) {
- my $cust_location = new FS::cust_location {
- map { $_ => scalar($cgi->param($_)) }
- qw( custnum address1 address2 city county state zip country geocode )
- };
- $opt{'cust_location'} = $cust_location;
-}
+my %hash = (
+ 'pkgpart' => $pkgpart,
+ 'quantity' => $quantity,
+ 'start_date' => ( scalar($cgi->param('start_date'))
+ ? parse_datetime($cgi->param('start_date'))
+ : ''
+ ),
+ 'refnum' => $refnum,
+ 'locationnum' => $locationnum,
+ 'discountnum' => $discountnum,
+ #for the create a new discount case
+ 'discountnum__type' => scalar($cgi->param('discountnum__type')),
+ 'discountnum_amount' => scalar($cgi->param('discountnum_amount')),
+ 'discountnum_percent' => scalar($cgi->param('discountnum_percent')),
+ 'discountnum_months' => scalar($cgi->param('discountnum_months')),
+ 'discountnum_setup' => scalar($cgi->param('discountnum_setup')),
+ 'contract_end' => ( scalar($cgi->param('contract_end'))
+ ? parse_datetime($cgi->param('contract_end'))
+ : ''
+ ),
+ 'waive_setup' => ( $cgi->param('waive_setup') eq 'Y' ? 'Y' : '' ),
+);
+$hash{'custnum'} = $cust_main->custnum if $cust_main;
+
+if ( $quotationnum ) {
+
+ $quotation_pkg = new FS::quotation_pkg \%hash;
+ $quotation_pkg->quotationnum($quotationnum);
+ $quotation_pkg->prospectnum($prospect_main->prospectnum) if $prospect_main;
-my $error = $cust_main->order_pkg( \%opt );
+ #XXX handle new location
+ $error = $quotation_pkg->insert;
+
+} else {
+
+ $cust_pkg = new FS::cust_pkg \%hash;
+
+ $cust_pkg->no_auto( scalar($cgi->param('no_auto')) );
+
+ my %opt = ( 'cust_pkg' => $cust_pkg );
+
+ if ( $locationnum == -1 ) {
+ my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) }
+ qw( custnum address1 address2 city county state zip country geocode )
+ };
+ $opt{'cust_location'} = $cust_location;
+ }
+
+ $error = $cust_main->order_pkg( \%opt );
+
+}
</%init>
diff --git a/httemplate/edit/process/quotation.html b/httemplate/edit/process/quotation.html
new file mode 100644
index 000000000..a69566581
--- /dev/null
+++ b/httemplate/edit/process/quotation.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'quotation',
+ 'redirect' => popurl(3).'view/quotation.html?',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation');
+
+</%init>
diff --git a/httemplate/edit/process/sales.cgi b/httemplate/edit/process/sales.cgi
new file mode 100644
index 000000000..edef4d65c
--- /dev/null
+++ b/httemplate/edit/process/sales.cgi
@@ -0,0 +1,23 @@
+<% include( 'elements/process.html',
+ 'table' => 'sales',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'debug' => '1',
+ 'process_m2m' => { 'link_table' => 'access_groupsales',
+ 'target_table' => 'access_group',
+ },
+ 'edit_ext' => 'cgi',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+if ( FS::Conf->new->exists('disable_acl_changes') ) {
+ errorpage('ACL changes disabled in public demo.');
+ die "shouldn't be reached";
+}
+
+</%init>
+
diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi
index a7d5136fb..41aca65ee 100755
--- a/httemplate/edit/process/svc_acct.cgi
+++ b/httemplate/edit/process/svc_acct.cgi
@@ -56,13 +56,14 @@ my $new = new FS::svc_acct ( \%hash );
my $error = '';
+my $part_svc = $svcnum ?
+ $old->part_svc :
+ qsearchs( 'part_svc',
+ { 'svcpart' => $cgi->param('svcpart') }
+ );
+
# google captcha auth
if ( $cgi->param('captcha_response') ) {
- my $part_svc = $svcnum ?
- $old->part_svc :
- qsearchs( 'part_svc',
- { 'svcpart' => $cgi->param('svcpart') }
- );
my ($export) = $part_svc->part_export('acct_google');
if ( $export and
! $export->captcha_auth($cgi->param('captcha_response')) ) {
@@ -79,6 +80,18 @@ if ( $cgi->param('clear_password') eq '*HIDDEN*'
}
if ( ! $error ) {
+
+ my $export_info = FS::part_export::export_info();
+
+ my @svc_export_machine =
+ map FS::svc_export_machine->new({
+ 'svcnum' => $svcnum,
+ 'exportnum' => $_->exportnum,
+ 'machinenum' => scalar($cgi->param('exportnum'.$_->exportnum.'machinenum')),
+ }),
+ grep { $_->machine eq '_SVC_MACHINE' }
+ $part_svc->part_export;
+
if ( $svcnum ) {
foreach ( grep { $old->$_ != $new->$_ }
qw( seconds upbytes downbytes totalbytes )
@@ -92,9 +105,9 @@ if ( ! $error ) {
$error ||= $new->set_usage(\%hash); #unoverlimit and trigger radius changes
last; #once is enough
}
- $error ||= $new->replace($old);
+ $error ||= $new->replace($old, 'child_objects'=>\@svc_export_machine);
} else {
- $error ||= $new->insert;
+ $error ||= $new->insert('child_objects'=>\@svc_export_machine);
$svcnum = $new->svcnum;
}
}
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
index 90eab4aad..25644e547 100644
--- a/httemplate/edit/process/svc_broadband.cgi
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -1,11 +1,10 @@
<& elements/svc_Common.html,
- table => 'svc_broadband',
- fields => [ fields('svc_broadband'), fields('nas'), 'usergroup' ],
+ table => 'svc_broadband',
+ fields => [ fields('svc_broadband'), fields('nas'), 'usergroup' ],
precheck_callback => \&precheck,
&>
<%init>
-# for historical reasons, process_m2m for usergroup tables is done
-# in the svc_x::insert/replace/delete methods, not here
+
my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html
index c8c8e98e1..d3985410b 100644
--- a/httemplate/edit/prospect_main.html
+++ b/httemplate/edit/prospect_main.html
@@ -36,6 +36,9 @@
'empty_label' => 'No address',
'disable_empty' => $conf->exists('prospect_main-location_required'),
'alt_format' => $conf->exists('prospect_main-alt_address_format'),
+ 'include_opt_callback' => sub {
+ 'prospect_main' => shift
+ },
},
],
'new_callback' => $new_callback,
diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html
index 27841063f..1d9647f2f 100644
--- a/httemplate/edit/quick-charge.html
+++ b/httemplate/edit/quick-charge.html
@@ -11,13 +11,30 @@
<SCRIPT TYPE="text/javascript">
-function enable_quick_charge () {
+function enable_quick_charge (e) {
+
if ( document.QuickChargeForm.amount.value
&& document.QuickChargeForm.pkg.value ) {
document.QuickChargeForm.submit.disabled = false;
} else {
document.QuickChargeForm.submit.disabled = true;
}
+
+% if ( $curuser->option('disable_enter_submit_onetimecharge') ) {
+
+ var key;
+ if (window.event)
+ key = window.event.keyCode; //IE
+ else
+
+ key = e.which; //firefox, others
+
+ return (key != 13);
+
+% } else {
+ return true;
+% }
+
}
function validate_quick_charge () {
@@ -76,7 +93,12 @@ function bill_now_changed (what) {
</SCRIPT>
-<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" ID="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();">
+<FORM ACTION = "process/quick-charge.cgi"
+ NAME = "QuickChargeForm"
+ ID = "QuickChargeForm"
+ METHOD = "POST"
+ onSubmit = "document.QuickChargeForm.submit.disabled=true; return validate_quick_charge();"
+>
<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
@@ -85,7 +107,13 @@ function bill_now_changed (what) {
<TR>
<TD ALIGN="right"><% mt('Amount') |h %> </TD>
<TD>
- <% $money_char %><INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()">
+ <% $money_char %><INPUT TYPE = "text"
+ NAME = "amount"
+ SIZE = 6
+ VALUE = "<% $amount %>"
+ onChange = "return enable_quick_charge(event)"
+ onKeyPress = "return enable_quick_charge(event)"
+ >
</TD>
</TR>
@@ -93,7 +121,11 @@ function bill_now_changed (what) {
<TR>
<TD ALIGN="right"><% mt('Quantity') |h %> </TD>
<TD>
- <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>">
+ <INPUT TYPE = "text"
+ NAME = "quantity"
+ SIZE = 4
+ VALUE = "<% $quantity %>"
+ onKeyPress = "return enable_quick_charge(event)">
</TD>
</TR>
% }
@@ -107,6 +139,7 @@ function bill_now_changed (what) {
NAME = "bill_now"
VALUE = "1"
<% $cgi->param('bill_now') ? 'CHECKED' : '' %>
+ onClick = "bill_now_changed(this);"
onChange = "bill_now_changed(this);"
>
<% mt('with terms') |h %>
@@ -127,7 +160,11 @@ function bill_now_changed (what) {
SIZE = 32
ID = "start_date_text"
VALUE = "<% $start_date %>"
- <% $cgi->param('bill_now') ? 'STYLE = "background-color:#dddddd" DISABLED' : '' %>
+ onKeyPress="return enable_quick_charge(event)"
+ <% $cgi->param('bill_now')
+ ? 'STYLE = "background-color:#dddddd" DISABLED'
+ : ''
+ %>
>
<IMG SRC = "<%$fsurl%>images/calendar.png"
ID = "start_date_button"
@@ -173,7 +210,14 @@ function bill_now_changed (what) {
<TR>
<TD ALIGN="right"><% mt('Description') |h %> </TD>
<TD>
- <INPUT TYPE="text" NAME="pkg" SIZE="50" MAXLENGTH="50" VALUE="<% $pkg %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()">
+ <INPUT TYPE = "text"
+ NAME = "pkg"
+ SIZE = "50"
+ MAXLENGTH = "50"
+ VALUE = "<% $pkg %>"
+ onChange = "return enable_quick_charge(event)"
+ onKeyPress = "return enable_quick_charge(event)"
+ >
</TD>
</TR>
@@ -191,7 +235,15 @@ function bill_now_changed (what) {
<TR>
<TD></TD>
<TD>
- <INPUT TYPE="text" NAME="description<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $param->{"description$row"} |h %>" rownum="<% $row %>" onkeyup = "possiblyAddRow;" >
+ <INPUT TYPE = "text"
+ NAME = "description<% $row %>"
+ SIZE = "60"
+ MAXLENGTH = "65"
+ VALUE = "<% $param->{"description$row"} |h %>"
+ rownum = "<% $row %>"
+ onKeyPress = "return enable_quick_charge(event)"
+ onKeyUp = "return possiblyAddRow(event)"
+ >
</TD>
</TR>
% }
@@ -210,10 +262,26 @@ function bill_now_changed (what) {
var rownum = <% $row %>;
- function possiblyAddRow() {
+ function possiblyAddRow(e) {
+
if ( ( rownum - this.getAttribute('rownum') ) == 1 ) {
addRow();
}
+
+% if ( $curuser->option('disable_enter_submit_onetimecharge') ) {
+
+ var key;
+ if (window.event)
+ key = window.event.keyCode; //IE
+ else
+ key = e.which; //firefox, others
+
+ return (key != 13);
+
+% } else {
+ return true;
+% }
+
}
function addRow() {
@@ -228,14 +296,16 @@ function bill_now_changed (what) {
var description_cell = document.createElement('TD');
- var description_input = document.createElement('INPUT');
- description_input.setAttribute('name', 'description'+rownum);
- description_input.setAttribute('id', 'description'+rownum);
- description_input.setAttribute('size', 60);
- description_input.setAttribute('maxLength', 65);
- description_input.setAttribute('rownum', rownum);
- description_input.onkeyup = possiblyAddRow;
- description_cell.appendChild(description_input);
+ //var description_input = document.createElement('INPUT');
+ var di = document.createElement('INPUT');
+ di.setAttribute('name', 'description'+rownum);
+ di.setAttribute('id', 'description'+rownum);
+ di.setAttribute('size', 60);
+ di.setAttribute('maxLength', 65);
+ di.setAttribute('rownum', rownum);
+ di.onkeyup = possiblyAddRow;
+ di.onkeypress = enable_quick_charge;
+ description_cell.appendChild(di);
row.appendChild(description_cell);
@@ -251,8 +321,10 @@ function bill_now_changed (what) {
</HTML>
<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('One-time charge');
+ unless $curuser->access_right('One-time charge');
my $conf = new FS::Conf;
my $date_format = $conf->config('date_format') || '%m/%d/%Y';
diff --git a/httemplate/edit/quotation.html b/httemplate/edit/quotation.html
new file mode 100644
index 000000000..8b6062355
--- /dev/null
+++ b/httemplate/edit/quotation.html
@@ -0,0 +1,30 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Quotation',
+ 'table' => 'quotation',
+ 'labels' => {
+ 'quotationnum' => 'Quotation number',
+ 'prospectnum' => 'Prospect',
+ 'custnum' => 'Customer',
+ '_date' => 'Date',
+ 'disabled' => 'Disabled',
+ },
+ 'fields' => [
+ { field=>'prospectnum', type=>'fixed-prospect_main' },
+ { field=>'custnum', type=>'fixed-cust_main' },
+ { field=>'_date', type=>'fixed-date' },
+ { field=>'disabled', type=>'checkbox', value=>'Y'},
+ ],
+ #XXX some way to disable the "view all"
+ 'new_callback' => sub { my( $cgi, $quotation) = @_;
+ $quotation->$_( $cgi->param($_) )
+ foreach qw( prospectnum custnum );
+ $quotation->_date(time);
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation');
+
+</%init>
diff --git a/httemplate/edit/radius_group.html b/httemplate/edit/radius_group.html
index 025561159..d3ef40c5d 100644
--- a/httemplate/edit/radius_group.html
+++ b/httemplate/edit/radius_group.html
@@ -7,7 +7,10 @@
'description' => 'Description',
'attrnum' => 'Attribute',
'priority' => 'Priority',
+ 'speed_down' => 'Download speed',
+ 'speed_up' => 'Upload speed',
},
+ 'viewall_dir' => 'browse',
'menubar' => \@menubar,
'edit_callback' => $edit_callback,
'error_callback' => $edit_callback,
@@ -27,6 +30,16 @@
'size' => 2,
'colspan' => 6, # just to not interfere with radius_attr columns
},
+ { 'field' => 'speed_down',
+ 'type' => 'text',
+ 'size' => 8,
+ 'colspan' => 6,
+ },
+ { 'field' => 'speed_up',
+ 'type' => 'text',
+ 'size' => 8,
+ 'colspan' => 6,
+ },
{
'field' => 'attrnum',
'type' => 'radius_attr',
diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html
index 620a2ea15..78d044755 100644
--- a/httemplate/edit/reason.html
+++ b/httemplate/edit/reason.html
@@ -1,50 +1,78 @@
-%
-% $cgi->param('class') =~ /^(\w)$/ or die "illegal class";
-% my $class=$1;
-%
-% my $classname = $FS::reason_type::class_name{$class};
-%
-% my (@types) = qsearch( 'reason_type', { 'class' => $class } );
-%
-% unless (scalar(@types)) {
-% print $cgi->redirect( "reason_type.html?class=$class" );
-% }
-<% include( 'elements/edit.html',
- 'name' => ucfirst($classname) . ' Reason',
- 'table' => 'reason',
- 'labels' => {
- 'reasonnum' => ucfirst($classname) . ' Reason',
- 'reason_type' => ucfirst($classname) . ' Reason type',
- 'reason' => ucfirst($classname) . ' Reason',
- 'disabled' => 'Disabled',
- 'class' => '',
- },
- 'fields' => [
- { 'field' => 'reason_type',
- 'type' => 'select',
- #XXX use something more sane than a hashref
- #then fix tr-select.html
- 'value' => { 'vcolumn' => 'typenum',
- 'ccolumn' => 'type',
- 'values' => \@types,
- },
- },
- 'reason',
- { 'field' => 'class',
- 'type' => 'hidden',
- 'value' => $class,
- },
- { 'field' => 'disabled',
- 'type' => 'checkbox',
- 'value' => 'Y'
- },
- ],
- 'viewall_url' => $p . "browse/reason.html?class=$class",
- )
-%>
+<& elements/edit.html,
+ 'menubar'=> [ "View all $classname Reasons" =>
+ $p.'browse/reason.html?class='.$class,
+ "View $classname Reason Types" =>
+ $p.'browse/reason_type.html?class='.$class,
+ ],
+ 'name' => ucfirst($classname) . ' Reason',
+
+ 'table' => 'reason',
+ 'labels' => {
+ 'reasonnum' => $classname . ' Reason',
+ 'reason_type' => $classname . ' Reason type',
+ 'reason' => $classname . ' Reason',
+ 'disabled' => 'Disabled',
+ 'class' => '',
+ 'unsuspend_pkgpart' => 'Unsuspension fee',
+ 'unsuspend_hold' => 'Delay until next bill',
+ },
+ 'fields' => \@fields,
+&>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+my $class=$1;
+
+my $classname = ucfirst($FS::reason_type::class_name{$class});
+
+my (@types) = qsearch( 'reason_type', { 'class' => $class } );
+
+unless (scalar(@types)) {
+ print $cgi->redirect( "reason_type.html?class=$class" );
+}
+
+my @fields = (
+ { 'field' => 'reason_type',
+ 'type' => 'select-table',
+ 'table' => 'reason_type',
+ 'name_col' => 'type',
+ 'value_col' => 'typenum',
+ 'hashref' => { 'class' => $class },
+ 'disable_empty' => 1,
+# #then fix tr-select.html
+#
+# 'value' => { 'vcolumn' => 'typenum',
+# 'ccolumn' => 'type',
+# 'values' => \@types,
+# },
+# # that wasn't so hard...did this do something else that I'm missing?
+ },
+ 'reason',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ 'value' => $class,
+ },
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y'
+ },
+);
+
+push @fields,
+ { 'field' => 'unsuspend_pkgpart',
+ 'type' => 'select-part_pkg',
+ 'hashref' => { 'disabled' => '',
+ 'freq' => 0 }, # one-time charges only
+ },
+ { 'field' => 'unsuspend_hold',
+ 'type' => 'checkbox',
+ 'value' => 'Y',
+ },
+ if ( $class eq 'S' );
+
+
+
</%init>
diff --git a/httemplate/edit/sales.cgi b/httemplate/edit/sales.cgi
new file mode 100755
index 000000000..3497de505
--- /dev/null
+++ b/httemplate/edit/sales.cgi
@@ -0,0 +1,79 @@
+<% include("/elements/header.html","$action Sales Person", menubar(
+ 'View all sales people' => $p. 'browse/sales.cgi',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM METHOD = POST
+ ACTION = "<%popurl(1)%>process/sales.cgi"
+>
+
+<INPUT TYPE="hidden" NAME="salesnum" VALUE="<% $sales->salesnum %>">
+Sales #<% $sales->salesnum ? $sales->salesnum : "(NEW)" %>
+
+<% &ntable("#cccccc", 2, '') %>
+
+ <TR>
+ <TH ALIGN="right">Sales</TH>
+ <TD><INPUT TYPE="text" NAME="salesperson" SIZE=32 VALUE="<% $sales->salesperson %>"></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right"><% emt('Agent') %></TD>
+ <TD>
+ <& /elements/select-agent.html,
+ 'curr_value' => $sales->salesnum,
+ 'disable_empty' => 1,
+ &>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Disable</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $sales->disabled eq 'Y' ? ' CHECKED' : '' %>></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Access Groups</TD>
+ <TD><% include('/elements/checkboxes-table.html',
+ 'source_obj' => $sales,
+ 'link_table' => 'access_groupsales',
+ 'target_table' => 'access_group',
+ 'name_col' => 'groupname',
+ 'target_link' => $p. 'edit/access_group.html?',
+ )
+ %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $sales->salesnum ? "Apply changes" : "Add sales" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $sales;
+if ( $cgi->param('error') ) {
+ $sales = new FS::sales ( {
+ map { $_, scalar($cgi->param($_)) } fields('sales')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $sales = qsearchs( 'sales', { 'salesnum' => $1 } );
+} else { #adding
+ $sales = new FS::sales {};
+}
+my $action = $sales->salesnum ? 'Edit' : 'Add';
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index 38567ef67..142c11150 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -173,6 +173,12 @@ function randomPass() {
<INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>">
%}
+<& /elements/tr-svc_export_machine.html,
+ 'svc' => $svc_acct,
+ 'part_svc' => $part_svc,
+ 'cgi' => $cgi,
+&>
+
% #uid/gid
% foreach my $xid (qw( uid gid )) {
%
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
index b266928a1..0d4b9897b 100644
--- a/httemplate/edit/svc_broadband.cgi
+++ b/httemplate/edit/svc_broadband.cgi
@@ -3,7 +3,7 @@
'name' => 'broadband service',
'table' => 'svc_broadband',
'fields' => \@fields,
- 'field_callback' => $field_callback,
+ 'svc_field_callback' => $svc_field_callback,
'svc_new_callback' => $svc_edit_callback,
'svc_edit_callback' => $svc_edit_callback,
'svc_error_callback' => $svc_edit_callback,
@@ -161,20 +161,14 @@ my $svc_edit_callback = sub {
}
};
-my $field_callback = sub {
+my $svc_field_callback = sub {
my ($cgi, $object, $fieldref) = @_;
my $columndef = $part_svc->part_svc_column($fieldref->{'field'});
- if ($columndef->columnflag eq 'F') {
- $fieldref->{'type'} = length($columndef->columnvalue)
- ? 'fixed'
- : 'hidden';
- $fieldref->{'value'} = $columndef->columnvalue;
+ if ($fieldref->{field} eq 'usergroup' && $columndef->columnflag eq 'F') {
- if ( $fieldref->{field} eq 'usergroup' ) {
- $fieldref->{'formatted_value'} =
- [ $object->radius_groups('long_description') ];
- }
+ $fieldref->{'formatted_value'} =
+ [ $object->radius_groups('long_description') ];
}
};
diff --git a/httemplate/edit/svc_dsl.cgi b/httemplate/edit/svc_dsl.cgi
index 1aeadb376..36345b9c5 100644
--- a/httemplate/edit/svc_dsl.cgi
+++ b/httemplate/edit/svc_dsl.cgi
@@ -52,12 +52,24 @@ my $edit_cb = sub {
elsif($export->exporttype eq 'ikano') {
@fields = ( 'password', 'monitored', );
- foreach my $hf ( keys %$ti_fields ) {
- push @fields, {
- field => $hf,
- type => 'hidden',
- value => $svc_x->$hf,
- } unless ( $hf eq 'password' || $hf eq 'monitored' );
+ if ( $svc_x->vendor_qual_id ) {
+ push @fields, { field => 'vendor_qual_id',
+ type => 'hidden',
+ value => $svc_x->vendor_qual_id,
+ };
+ } else {
+ push @fields, 'vendor_qual_id';
+ }
+
+ foreach my $hf (
+ grep { $_ !~ /^(password|monitored|vendor_qual_id)$/ }
+ keys %$ti_fields
+ ) {
+ push @fields, {
+ field => $hf,
+ type => 'hidden',
+ value => $svc_x->$hf,
+ };
}
}
# else add any other export-specific stuff here
diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html
index 5a0f2a8ce..03b488e86 100644
--- a/httemplate/edit/tower.html
+++ b/httemplate/edit/tower.html
@@ -12,12 +12,16 @@
m2_label => 'Sector',
m2_error_callback => $m2_error_callback,
},
+ 'latitude',
+ 'longitude',
],
labels => { 'towernum' => 'Tower',
'towername' => 'Name',
'sectornum' => 'Sector',
'disabled' => 'Disabled',
'default_ip_addr' => 'Tower IP address',
+ 'latitude' => 'Latitude',
+ 'longitude' => 'Longitude',
},
&>
<%init>
diff --git a/httemplate/elements/coord-links.html b/httemplate/elements/coord-links.html
index 3fd3ff696..02a224a00 100644
--- a/httemplate/elements/coord-links.html
+++ b/httemplate/elements/coord-links.html
@@ -3,7 +3,7 @@
<& /elements/popup_link.html,
'action' => $p. 'view/map.html?'. $query,
'label' => mt('map'),
- 'actionlabel' => $name,
+ 'actionlabel' => $js_name,
'width' => 763,
'height' => 575,
#'color'
@@ -12,8 +12,8 @@
% if ( $origin ) {
<& /elements/popup_link.html,
'action' => $p. "view/directions.html?origin=$origin;". $query,
- 'label' => mt('dir'),
- 'actionlabel' => $name,
+ 'label' => mt('directions'),
+ 'actionlabel' => $js_name,
'width' => 763,
'height' => 575,
&>
@@ -29,6 +29,12 @@ my $query = 'name='. uri_escape_utf8($name).
';lat='. $latitude.
';lon='. $longitude;
+my $js_name = $name;
+$js_name =~ s/[<>"]/ /g;
+$m->interp->apply_escapes($js_name, 'js_string');
+$js_name =~ s/^'//;
+$js_name =~ s/'$//;
+
my $origin;
#for directions link
if ( $agentnum =~ /^\d+$/ ) {
diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html
index a517ece2a..75e682d92 100644
--- a/httemplate/elements/customer-table.html
+++ b/httemplate/elements/customer-table.html
@@ -41,6 +41,8 @@ Example:
<SCRIPT TYPE="text/javascript">
+ var num_open_invoices = new Array;
+
function clearhint_invnum() {
if ( this.value == 'Not found' || this.value == 'Multiple' ) {
@@ -90,7 +92,7 @@ Example:
customer_select.style.display = 'none';
return false;
- } else if ( customerArray.length == 5 ) {
+ } else if ( customerArray.length == 6 ) {
custnum_obj.value = customerArray[0];
custnum_obj.style.color = '#000000';
@@ -99,6 +101,7 @@ Example:
update_balance_text(searchrow, customerArray[2]);
update_status_text( searchrow, customerArray[3]);
update_status_color(searchrow, '#'+customerArray[4]);
+ update_num_open(searchrow, customerArray[5]);
customer.style.display = '';
customer_select.style.display = 'none';
@@ -140,6 +143,7 @@ Example:
update_balance_text(searchrow, '');
update_status_text(searchrow, '');
update_status_color(searchrow, '#000000');
+ update_num_open(searchrow, 0);
function search_invnum_update(customers) {
@@ -175,15 +179,16 @@ Example:
if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
<% $opt{prefix} %>addRow();
}
- var customer = document.getElementById('customer'+searchrow);
- customer.value = 'searching...';
- customer.disabled = true;
- customer.style.color = '#000000';
- customer.style.backgroundColor = '#dddddd';
+
+ var customer_obj = document.getElementById('customer'+searchrow);
+ customer_obj.value = 'searching...';
+ customer_obj.disabled = true;
+ customer_obj.style.color = '#000000';
+ customer_obj.style.backgroundColor = '#dddddd';
var customer_select = document.getElementById('cust_select'+searchrow);
- customer.style.display = '';
+ customer_obj.style.display = '';
customer_select.style.display = 'none';
var invnum = document.getElementById('invnum'+searchrow);
@@ -192,15 +197,48 @@ Example:
update_balance_text(searchrow, '');
update_status_text( searchrow, '');
update_status_color(searchrow, '#000000');
+ update_num_open(searchrow, 0);
function search_custnum_update(customers) {
- var customerArray = eval('(' + customers + ')') || [];
- update_customer(searchrow, customerArray);
+ var customerArrayArray = eval('(' + customers + ')') || [];
+
+ if ( customerArrayArray.length == 0 ) {
+
+ update_customer(searchrow, []);
+
+ } else if ( customerArrayArray.length == 1 ) {
+ update_customer(searchrow, customerArrayArray[0]);
% if ( $opt{custnum_update_callback} ) {
- <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
+ <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
% }
+
+ } else {
+
+ custnum_obj.value = 'Multiple'; // or something
+ custnum_obj.style.color = '#ff0000';
+
+ //blank the current list
+ customer_select.options.length = 0;
+
+ opt(customer_select, '', 'Multiple customers match "' + custnum + '" - select one', '#ff0000');
+ //add the multiple customers
+ for ( var s = 0; s < customerArrayArray.length; s++ ) {
+ opt(customer_select,
+ JSON.stringify(customerArrayArray[s]),
+ customerArrayArray[s][1],
+ '#000000');
+ }
+
+ opt(customer_select, 'cancel', '(Edit search string)', '#000000');
+
+ customer_obj.style.display = 'none';
+
+ customer_select.style.display = '';
+
+ }
+
}
custnum_search(custnum, search_custnum_update );
@@ -243,9 +281,13 @@ Example:
custnum_obj.disabled = false;
custnum_obj.style.backgroundColor = '#ffffff';
- if ( customerArrayArray.length == 1 ) {
+ if ( customerArrayArray.length == 0 ) {
+
+ update_customer(searchrow, []);
+
+ } else if ( customerArrayArray.length == 1 ) {
- update_customer(customerArrayArray[1]);
+ update_customer(searchrow, customerArrayArray[0]);
% if ( $opt{custnum_update_callback} ) {
<% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
% }
@@ -337,6 +379,9 @@ Example:
document.getElementById('balance'+rownum+'_text').innerHTML = newval;
}
+ function update_num_open(rownum, newval) {
+ num_open_invoices[rownum] = newval;
+ }
</SCRIPT>
@@ -356,7 +401,7 @@ Example:
% my $row = 0;
% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) {
- <TR>
+ <TR id="row<%$row%>" rownum="<%$row%>">
<TD>
<INPUT TYPE = "text"
NAME = "invnum<% $row %>"
@@ -458,19 +503,24 @@ Example:
% my $color = $opt{color}->[$col];
% my $font = $color ? qq(<FONT COLOR="$color">) : '';
% my $onchange = '';
-% if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+% if ( $opt{onchange}->[$col] ) {
+% $onchange = 'onchange="'.$opt{onchange}->[$col].'"';
+% }
+% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
% $total[$col] += $value;
% $onchange = $opt{prefix}. "calc_total$col();";
% $onchange = qq(onchange="$onchange" onkeyup="$onchange");
% }
<TD ALIGN="<% $align %>">
-% if (! $types->[$col] || $types->[$col] eq 'text') {
- <INPUT TYPE = "text"
+% my $type = $types->[$col] || 'text';
+% if ($type eq 'text' or $type eq 'checkbox') {
+ <INPUT TYPE = "<% $type %>"
NAME = "<% $name %>"
ID = "<% $name %>"
SIZE = "<% $size %>"
STYLE = "text-align: <% $align %>;"
VALUE = "<% $value %>"
+ rownum = "<% $row %>"
<% $onchange %>
>
% } elsif ($types->[$col] eq 'immutable') {
@@ -485,7 +535,7 @@ Example:
</TR>
% }
-<TR>
+<TR id="row_total">
<TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
Total <% $row ? $row-1 : 0 %>
<% PL($opt{name_singular} || 'customer', ( $row ? $row-1 : 0 ) ) %>
@@ -559,7 +609,8 @@ Example:
var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
var tablebody = table.getElementsByTagName('tbody').item(0);
- var row = table.insertRow(rownum+1);
+ var row = table.insertRow(table.rows.length - 1);
+ row.setAttribute('id', 'row'+rownum);
var invnum_cell = document.createElement('TD');
@@ -676,7 +727,7 @@ Example:
% } else {
% $value = $param->{"$field$row"};
% }
- var my_text = document.createTextNode('<% $value %>');
+ var my_text = document.createTextNode(<% $value |js_string %>);
my_cell.appendChild(my_text);
% }
@@ -686,10 +737,17 @@ Example:
my_input.setAttribute('id', '<% $name %>'+<% $opt{prefix} %>rownum);
my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
-% if ($types->[$col] eq 'immutable') {
+ my_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+% if ( $types->[$col] eq 'immutable' ) {
my_input.setAttribute('type', 'hidden');
% }
-% if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+% elsif ( $types->[$col] eq 'checkbox' ) {
+ my_input.setAttribute('type', 'checkbox');
+% }
+% if ( $opt{onchange}->[$col] ) {
+ my_input.onchange = <% $opt{onchange}->[$col] %>;
+% }
+% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
my_input.onchange = <% $opt{prefix} %>calc_total<%$col%>;
my_input.onkeyup = <% $opt{prefix} %>calc_total<%$col%>;
% }
@@ -713,6 +771,11 @@ Example:
+ ' <% PL($opt{name_singular} || 'customer') %>';
}
+% if ( $opt{add_row_callback} ) {
+ <% $opt{add_row_callback} %>(<% $opt{prefix} %>rownum,
+ '<% $opt{prefix} %>');
+% }
+
<% $opt{prefix} %>rownum++;
}
diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html
index 72f596f4a..c6362e0c9 100644
--- a/httemplate/elements/dashboard-toplist.html
+++ b/httemplate/elements/dashboard-toplist.html
@@ -32,18 +32,21 @@
</FONT>
</TD>
-% foreach my $priority ( @custom_priorities, '' ) {
-% my $num =
-% FS::TicketSystem->num_customer_tickets($custnum,$priority);
-% my $ahref = '';
-% $ahref= '<A HREF="'.
-% FS::TicketSystem->href_customer_tickets($custnum,$priority).
-% '">'
-% if $num;
-
+% foreach my $priority ( @custom_priorities ) {
<TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
- <% $ahref.$num %></A>
- </TD>
+% my $num = $num_tickets_by_priority{$priority}->{$custnum};
+% if ( $num ) {
+ <A HREF="<%
+ FS::TicketSystem->href_customer_tickets($custnum,$priority)
+ %>"><% $num %></A>
+% if ( $priority &&
+% exists($num_tickets_by_priority{''}{$custnum}) ) {
+% # decrement the customer's total by the number in
+% # this priority bin
+% $num_tickets_by_priority{''}{$custnum} -= $num;
+% }
+% }
+ </TD>
% }
</TR>
@@ -77,7 +80,7 @@
<TH CLASS="grid" BGCOLOR="#cccccc"><% $line %></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Lint') |h %></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"></TH>
-% foreach my $priority ( @custom_priorities, '' ) {
+% foreach my $priority ( @custom_priorities ) {
<TH CLASS="grid" BGCOLOR="#cccccc">
<% $priority || '<i>(none)</i>'%>
</TH>
@@ -105,11 +108,83 @@ my $conf = new FS::Conf;
#false laziness w/httemplate/search/cust_main.cgi... care if
# custom_priority_field becomes anything but a local hack...
+
my @custom_priorities = ();
-if ( $conf->config('ticket_system-custom_priority_field')
+my $custom_priority_field = $conf->config('ticket_system-custom_priority_field');
+if ( $custom_priority_field
&& @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) {
@custom_priorities =
$conf->config('ticket_system-custom_priority_field-values');
}
-
+push @custom_priorities, '';
+
+my %num_tickets_by_priority = map { $_ => {} } @custom_priorities;
+# "optimization" (i.e. "terrible hack") to avoid constructing
+# (@custom_priorities) x (cust_main) queries with a bazillion
+# joins each just to count tickets
+if ( $FS::TicketSystem::system eq 'RT_Internal'
+ and $conf->config('dashboard-toplist') )
+{
+ my $text = (driver_name =~ /^Pg/) ? 'text' : 'char';
+ # The RT API does not play nicely with aggregate queries,
+ # so we're going to go around it.
+ my $sql;
+ # optimization to keep this from taking a million years
+ my $cust_tickets =
+ "SELECT custnum, Tickets.Id, Tickets.Queue
+ FROM cust_main
+ JOIN Links ON (
+ Links.Target = 'freeside://freeside/cust_main/' || CAST(cust_main.custnum AS $text)
+ AND Links.Base LIKE '%rt://%/ticket/%'
+ AND Links.Type = 'MemberOf'
+ ) JOIN Tickets ON (Links.LocalBase = Tickets.Id)
+ UNION
+ SELECT custnum, Tickets.Id, Tickets.Queue
+ FROM cust_pkg JOIN cust_svc USING (pkgnum)
+ JOIN Links ON (
+ Links.Target = 'freeside://freeside/cust_svc/' || CAST(cust_svc.svcnum AS $text)
+ AND Links.Base LIKE '%rt://%/ticket/%'
+ AND Links.Type = 'MemberOf'
+ ) JOIN Tickets ON (Links.LocalBase = Tickets.Id)
+ ";
+
+ if ( $custom_priority_field ) {
+ $sql =
+ "SELECT cust_tickets.custnum AS custnum,
+ ObjectCustomFieldValues.Content as priority,
+ COUNT(DISTINCT cust_tickets.Id) AS num_tickets
+ FROM ($cust_tickets) AS cust_tickets
+ LEFT JOIN ObjectCustomFields ON (
+ ObjectCustomFields.ObjectId = '0' OR
+ ObjectCustomFields.ObjectId = cust_tickets.Queue
+ )
+ LEFT JOIN CustomFields ON (
+ ObjectCustomFields.CustomField = CustomFields.Id AND
+ CustomFields.Name = '$custom_priority_field'
+ )
+ LEFT JOIN ObjectCustomFieldValues ON (
+ ObjectCustomFieldValues.CustomField = CustomFields.Id AND
+ ObjectCustomFieldValues.ObjectType = 'RT::Ticket' AND
+ ObjectCustomFieldValues.Disabled = '0' AND
+ ObjectCustomFieldValues.ObjectId = cust_tickets.Id
+ )
+ GROUP BY cust_tickets.custnum, ObjectCustomFieldValues.Content";
+ #warn $sql."\n";
+ } else { # no custom_priority_field
+ $sql =
+ "SELECT cust_tickets.custnum,
+ '' as priority,
+ COUNT(DISTINCT cust_tickets.Id) AS num_tickets
+ FROM ($cust_tickets) AS cust_tickets
+ GROUP BY cust_tickets.custnum";
+ }
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ while ( my $row = $sth->fetchrow_hashref ) {
+ #warn to_json($row)."\n";
+ $num_tickets_by_priority{ $row->{priority} }->{ $row->{custnum} } =
+ $row->{num_tickets};
+ }
+}
+#warn Dumper \%num_tickets_by_priority;
</%init>
diff --git a/httemplate/elements/form-create_ticket.html b/httemplate/elements/form-create_ticket.html
new file mode 100644
index 000000000..362e82397
--- /dev/null
+++ b/httemplate/elements/form-create_ticket.html
@@ -0,0 +1,38 @@
+<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline">
+<SCRIPT TYPE="text/javascript">
+function updateTicketLink() {
+ var link = document.getElementById('CreateTicketLink');
+ var selector = document.getElementById('Queue')
+ link.href = "<% $new_base.'?'.
+ join(';', map(
+ { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"}
+ keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value;
+}
+</SCRIPT>
+<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A>
+<A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A>
+ <% mt('in queue') |h %>
+%# fetch list of queues in which the user can create tickets
+% my %queues = FS::TicketSystem->queues('', 'CreateTicket');
+% if( $conf->exists('ticket_system-force_default_queueid') ) {
+<B><% $queues{$new_param{'Queue'}} %></B>
+<INPUT TYPE="hidden" NAME="Queue" VALUE="<% $new_param{'Queue'} %>">
+% }
+% else {
+<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()">
+% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) {
+ <OPTION VALUE="<% $queueid %>"
+ <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
+ ><% $queues{$queueid} |h %>
+% }
+</SELECT>
+<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT>
+% }
+</FORM>
+<%init>
+my %opt = @_;
+my $conf = new FS::Conf;
+my $object = $opt{'object'}; # must be a cust_main, cust_svc, or svc_...
+my ($new_base, %new_param) = FS::TicketSystem->href_params_new_ticket($object);
+my $new_link = FS::TicketSystem->href_new_ticket($object);
+</%init>
diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css
index c291e1e33..82eb9b562 100644
--- a/httemplate/elements/freeside.css
+++ b/httemplate/elements/freeside.css
@@ -108,8 +108,8 @@ a.fstab {
-moz-border-radius-topright:8px;
-webkit-border-radius-topleft:8px;
-webkit-border-radius-topright:8px;
- border-radius-topleft:8px;
- border-radius-topright:8px;
+ border-top-left-radius:8px;
+ border-top-right-radius:8px;
/*font-weight:bold;*/
/*padding-left:12px;
padding-right:12px;*/
@@ -141,8 +141,8 @@ a.fstabselected {
-moz-border-radius-topright:8px;
-webkit-border-radius-topleft:8px;
-webkit-border-radius-topright:8px;
- border-radius-topleft:8px;
- border-radius-topright:8px;
+ border-top-left-radius:8px;
+ border-top-right-radius:8px;
/*font-weight:bold;*/
/*padding-left:12px;
padding-right:12px;*/
@@ -175,8 +175,8 @@ div.fstabcontainer {
-moz-border-radius-bottomright:8px;
-webkit-border-radius-bottomleft:8px;
-webkit-border-radius-bottomright:8px;
- border-radius-bottomleft:8px;
- border-radius-bottomright:8px;
+ border-bottom-left-radius:8px;
+ border-bottom-right-radius:8px;
-moz-box-shadow: #666666 1px 1px 2px;
-webkit-box-shadow: #666666 1px 1px 2px;
box-shadow: #666666 1px 1px 2px;
@@ -206,8 +206,8 @@ div.fstabcontainer {
-moz-border-radius-bottomright:8px;
-webkit-border-radius-bottomleft:8px;
-webkit-border-radius-bottomright:8px;
- border-radius-bottomleft:8px;
- border-radius-bottomright:8px;
+ border-bottom-left-radius:8px;
+ border-bottom-right-radius:8px;
-moz-box-shadow: #666666 1px 1px 2px;
-webkit-box-shadow: #666666 1px 1px 2px;
box-shadow: #666666 1px 1px 2px;
@@ -238,8 +238,8 @@ div.fstabcontainer {
-moz-border-radius-topright:8px;
-webkit-border-radius-topleft:8px;
-webkit-border-radius-topright:8px;
- border-radius-topleft:8px;
- border-radius-topright:8px;
+ border-top-left-radius:8px;
+ border-top-right-radius:8px;
-moz-box-shadow: 1px 0px 1px #999999;
-webkit-box-shadow: 1px 0px 1px #999999;
box-shadow: 1px 0px 1px #999999;
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
index d0ab3055d..c6ad3c387 100644
--- a/httemplate/elements/header.html
+++ b/httemplate/elements/header.html
@@ -42,6 +42,12 @@ Example:
<% include('init_overlib.html') |n %>
<% include('rs_init_object.html') |n %>
<% include('logout.html') |n %>
+% my $timeout = $conf->config('logout-timeout');
+% if ( $timeout && $timeout =~ /^\s*\d+\s*$/ ) {
+ <script type="text/javascript">
+ setTimeout('logout()', <% 60000 * $timeout %>);
+ </script>
+% }
<% $head |n %>
diff --git a/httemplate/elements/init_overlib.html b/httemplate/elements/init_overlib.html
index d27ca3bda..986adec40 100644
--- a/httemplate/elements/init_overlib.html
+++ b/httemplate/elements/init_overlib.html
@@ -1,9 +1,16 @@
% for my $file (@files) {
<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/<%$file%>.js"></SCRIPT>
% }
+<%shared>
+my $initialized = 0; #won't work if component is "preloaded"... so don't do that
+</%shared>
<%init>
-my @files = map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
-push @files, map { "${_}contentmws" } qw( iframe ajax );
+my @files = ();
+if ( ! $initialized ) {
+ push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
+ push @files, map { "${_}contentmws" } qw( iframe ajax );
+ $initialized++;
+}
</%init>
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index f3640510a..4df218b43 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -3,16 +3,18 @@
Example:
include( '/elements/location.html',
- 'object' => $cust_main, # or $cust_location
- 'prefix' => $pre, #only for cust_main objects
+ 'object' => $cust_location
+ 'prefix' => $pre, # prefixed to form field names
'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
+ 'enable_coords' => 1, #show latitude/longitude fields
+ 'enable_district' => 1, #show tax district field
+ 'enable_censustract' => 1, #show censustract field
+
)
</%doc>
@@ -40,12 +42,12 @@ Example:
% }
<TR>
- <<%$th%> ALIGN="right"><%$r%><% $opt{'address1_label'} || emt('Address') %></<%$th%>>
+ <<%$th%> STYLE="width:16ex" ALIGN="right"><%$r%><% $opt{'address1_label'} || emt('Address') %></<%$th%>>
<TD COLSPAN=7>
<INPUT TYPE = "text"
NAME = "<%$pre%>address1"
ID = "<%$pre%>address1"
- VALUE = "<% $object->get($pre.'address1') |h %>"
+ VALUE = "<% $object->get('address1') |h %>"
SIZE = 54
onChange = "<% $onchange %>"
<% $disabled %>
@@ -62,7 +64,7 @@ Example:
<INPUT TYPE = "text"
NAME = "<%$pre%>address2"
ID = "<%$pre%>address2"
- VALUE = "<% $object->get($pre.'address2') |h %>"
+ VALUE = "<% $object->get('address2') |h %>"
SIZE = 54
onChange = "<% $onchange %>"
<% $disabled %>
@@ -75,7 +77,7 @@ Example:
<INPUT TYPE = "hidden"
NAME = "<%$pre%>address2"
- VALUE = "<% $object->get($pre.'address2') |h %>"
+ VALUE = "<% $object->get('address2') |h %>"
>
<TR>
@@ -83,7 +85,7 @@ Example:
<TD COLSPAN=7>
% my $location_type = scalar($cgi->param('location_type'))
-% || $object->get($pre.'location_type');
+% || $object->get('location_type');
% #my $location_number = scalar($cgi->param('location_number'))
% # || $object->get($pre.'location_number');
%
@@ -130,7 +132,7 @@ Example:
<INPUT TYPE="text"
NAME = "location_number"
ID = "location_number"
- VALUE = "<% scalar($cgi->param('location_number')) || $object->get($pre.'location_number') |h %>"
+ VALUE = "<% scalar($cgi->param('location_number')) || $object->get('location_number') |h %>"
SIZE = "5"
<% $disabled || ($location_type ? '' : 'DISABLED') %>
<% $style %>
@@ -161,7 +163,7 @@ Example:
<INPUT TYPE = "text"
NAME = "<%$pre%>zip"
ID = "<%$pre%>zip"
- VALUE = "<% $object->get($pre.'zip') |h %>"
+ VALUE = "<% $object->get('zip') |h %>"
SIZE = 11
onChange = "<% $onchange %>"
<% $disabled %>
@@ -175,13 +177,14 @@ Example:
<TD COLSPAN=6><% include('/elements/select-country.html', %select_hash ) %></TD>
</TR>
+% if ( $opt{enable_coords} ) {
<TR>
<TD ALIGN="right"><% mt('Latitude') |h %></TH>
<TD COLSPAN=7>
<INPUT TYPE = "text"
NAME = "<%$pre%>latitude"
ID = "<%$pre%>latitude"
- VALUE = "<% $object->get($pre.'latitude') |h %>"
+ VALUE = "<% $object->get('latitude') |h %>"
<% $disabled %>
<% $style %>
>
@@ -189,37 +192,48 @@ Example:
<INPUT TYPE = "text"
NAME = "<%$pre%>longitude"
ID = "<%$pre%>longitude"
- VALUE = "<% $object->get($pre.'longitude') |h %>"
+ VALUE = "<% $object->get('longitude') |h %>"
<% $disabled %>
<% $style %>
>
</TD>
</TR>
-<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->get($pre.'coord_auto') %>">
-
-% 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="enter_censustract" VALUE="<% $opt{censustract} %>">
- <INPUT TYPE="hidden" NAME="censusyear" VALUE="<% $object->get('censusyear') %>">
- <INPUT TYPE="hidden" NAME="censustract" VALUE="">
- </TD>
- </TR>
+% foreach (qw(latitude longitude)) {
+<INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $object->get($_) |h%>">
+% }
+% }
+<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>">
+
+<INPUT TYPE="hidden" NAME="<%$pre%>geocode" VALUE="<% $object->geocode %>">
+<INPUT TYPE="hidden" NAME="<%$pre%>censustract" VALUE="<% $object->censustract %>">
+<INPUT TYPE="hidden" NAME="<%$pre%>censusyear" VALUE="<% $object->censusyear %>">
+% if ( $opt{enable_censustract} ) {
+<TR>
+ <TD ALIGN="right">Census&nbsp;tract</TD>
+ <TD COLSPAN=8>
+ <INPUT TYPE="text" SIZE=15
+ NAME="<%$pre%>enter_censustract"
+ VALUE="<% $object->censustract %>">
+ <% '(automatic)' %>
+ </TD>
+</TR>
+% }
+% if ( $conf->config('tax_district_method') ) {
+% if ( $opt{enable_district} ) {
+ <TR>
+ <TD ALIGN="right">Tax&nbsp;district</TD>
+ <TD COLSPAN=8>
+ <INPUT TYPE="text" SIZE=15
+ NAME="<%$pre%>district"
+ VALUE="<% $object->district %>">
+ <% '(automatic)' %>
+ </TD>
+ </TR>
% } else {
- <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>">
-% }
-% if ( $conf->config('tax_district_method') or $object->get('district') ) {
- <TR>
- <<%$th%> ALIGN="right">Tax district<BR>(automatic)</<%$th%>>
- <TD>
- <INPUT TYPE="text" NAME="district" VALUE="<%$object->get('district')%>">
- </TD>
- </TR>
+ <INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>">
% }
-% }
+% }
%# For address standardization:
%# keep a clean copy of the address so we know if we need
@@ -244,16 +258,13 @@ my $conf = new FS::Conf;
my $r = $opt{'no_asterisks'} ? '' : qq!<font color="#ff0000">*</font>&nbsp;!;
-#false laziness with ship state
my $countrydefault = $conf->config('countrydefault') || 'US';
-$object->set($pre.'country', $countrydefault )
- unless $object->get($pre.'country');
-
-my $statedefault = $conf->config('statedefault')
+my $statedefault = $conf->config('statedefault')
|| ($countrydefault eq 'US' ? 'CA' : '');
-$object->set($pre.'state', $statedefault )
- unless $object->get($pre.'state')
- || $object->get($pre.'country') ne $countrydefault;
+$object ||= FS::cust_location->new({
+ 'country' => $countrydefault,
+ 'state' => $statedefault,
+});
my $alt_err = ($opt{'alt_format'} && !$disabled) ? $object->alternize : '';
@@ -266,8 +277,8 @@ push @address2_label_style, 'visibility:hidden'
|| ! $conf->exists('cust_main-require_address2')
|| ( !$pre && !$opt{'same_checked'} );
-my @counties = counties( $object->get($pre.'state'),
- $object->get($pre.'country'),
+my @counties = counties( $object->get('state'),
+ $object->get('country'),
);
my @county_style = ();
push @county_style, 'display:none' # 'visibility:hidden'
@@ -287,10 +298,10 @@ my $county_style =
: '';
my %select_hash = (
- 'city' => $object->get($pre.'city'),
- 'county' => $object->get($pre.'county'),
- 'state' => $object->get($pre.'state'),
- 'country' => $object->get($pre.'country'),
+ 'city' => $object->get('city'),
+ 'county' => $object->get('county'),
+ 'state' => $object->get('state'),
+ 'country' => $object->get('country'),
'prefix' => $pre,
'onchange' => $onchange,
'disabled' => $disabled,
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index b1cbebf34..b2141e991 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -94,6 +94,11 @@ tie my %report_prospects, 'Tie::IxHash',
'Advanced prospect reports' => [ $fsurl. 'search/report_prospect_main.html', '' ],
;
+tie my %report_quotations, 'Tie::IxHash',
+ 'List quotations' => [ $fsurl. 'search/quotation.html', '' ],
+ 'Advanced quotation reports' => [ $fsurl. 'search/report_quotation.html', '' ],
+;
+
tie my %report_customers_lists, 'Tie::IxHash',
'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ],
'by last name' => [ $fsurl. 'search/cust_main.cgi?browse=last', '' ],
@@ -110,8 +115,7 @@ $report_customers{'List customers'} = [ \%report_customers_lists, 'List customer
$report_customers{'Zip code distribution'} = [ $fsurl. 'search/report_cust_main-zip.html', 'Zip codes by number of customers' ];
$report_customers{'Customer signup report'} = [ $fsurl. 'graph/report_cust_signup.html', 'New customer signups by date' ],
$report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_main.html', 'by status, signup date, agent, etc.' ]
- if $curuser->access_right('List customers')
- && $curuser->access_right('List packages');
+ if $curuser->access_right('Advanced customer search');
tie my %report_invoices_open, 'Tie::IxHash',
'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ],
@@ -201,10 +205,10 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
];
}
- if ( $svcdb =~ /^svc_(acct|broadband|hardware)$/ ) {
$report_svc{"Advanced $lcsname reports"} =
- [ $fsurl."search/report_$svcdb.html", '' ];
- }
+ [ $fsurl."search/report_$svcdb.html", '' ]
+ if $svcdb =~ /^svc_(acct|broadband|hardware)$/
+ && $curuser->access_right("Services: $name: Advanced search");
if ( $svcdb eq 'svc_phone' ) {
@@ -221,7 +225,8 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
}
- $report_services{$name} = [ \%report_svc, $longname ];
+ $report_services{$name} = [ \%report_svc, $longname ]
+ if $curuser->access_right("Services: $name");
}
@@ -253,14 +258,19 @@ tie my %report_inventory, 'Tie::IxHash',
'Inventory activity' => [ $fsurl.'search/report_h_inventory_item.html', '' ],
;
-tie my %report_rating, 'Tie::IxHash',
- 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ],
- 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ],
- 'Unrateable CDRs' => [ $fsurl.'search/cdr.html?freesidestatus=failed'.
- ';cdrbatchnum=_ALL_' ],
- 'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ],
- 'Time worked summary' => [ $fsurl.'search/report_rt_ticket.html', '' ],
-;
+tie my %report_rating, 'Tie::IxHash';
+$report_rating{'RADIUS sessions'} = [ $fsurl.'search/sqlradius.html', '' ]
+ if $curuser->access_right("Usage: RADIUS sessions");
+$report_rating{'RADIUS data usage'} = [ $fsurl.'search/report_sqlradius_usage.html', '' ]
+ if $curuser->access_right("Usage: RADIUS sessions");
+$report_rating{'Call Detail Records (CDRs)'} = [ $fsurl.'search/report_cdr.html', '' ]
+ if $curuser->access_right("Usage: Call Detail Records (CDRs)");
+$report_rating{'Unrateable CDRs'} = [ $fsurl.'search/cdr.html?freesidestatus=failed;cdrbatchnum=_ALL_' ]
+ if $curuser->access_right("Usage: Unrateable CDRs");
+if ( $curuser->access_right("Usage: Time worked") ) {
+ $report_rating{'Time worked'} = [ $fsurl.'search/report_rt_transaction.html', '' ];
+ $report_rating{'Time worked summary'} = [ $fsurl.'search/report_rt_ticket.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' ],
@@ -272,13 +282,14 @@ tie my %report_ticketing_statistics, 'Tie::IxHash',
;
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' => '',
+# fix TimeToResolve extension? or redo in a more modern way?
+# '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' ],
+ 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html?NewQuery=1', 'List tickets by any criteria' ],
;
tie my %report_bill_event, 'Tie::IxHash',
@@ -310,6 +321,7 @@ if($curuser->access_right('Financial reports')) {
'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ],
'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ],
'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ],
+ 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ],
'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ],
'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
'Unapplied Credits' => [ $fsurl.'search/report_cust_credit.html?unapplied=1', 'Unapplied credit report (by type and/or date range)' ],
@@ -337,6 +349,8 @@ if($curuser->access_right('Financial reports')) {
tie my %report_menu, 'Tie::IxHash';
$report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ]
if $curuser->access_right('List prospects');
+$report_menu{'Quotations'} = [ \%report_quotations, 'Quotation reports' ]
+ if $curuser->access_right('List quotations');
$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ]
if $curuser->access_right('List customers');
$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ]
@@ -387,10 +401,18 @@ tie my %tools_exporting, 'Tie::IxHash',
'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ],
;
+tie my %tools_ticketing_articles, 'Tie::IxHash',
+ 'Overview' => [ $fsurl.'rt/Articles/index.html', '' ],
+ 'Search' => [ $fsurl.'rt/Articles/Article/Search.html', '' ],
+ 'Topics' => [ $fsurl.'rt/Articles/Topics.html', '' ],
+;
+
tie my %tools_ticketing, 'Tie::IxHash',
- 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ],
+ 'Articles' => [ \%tools_ticketing_articles, '' ],
'My Day' => [ $fsurl.'rt/Tools/MyDay.html', '' ],
- 'My Approvals' => [ $fsurl.'rt/Approvals/', '' ],
+ 'My Reminders' => [ $fsurl.'rt/Tools/MyReminders.html', '' ],
+ 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ],
+ 'Approval' => [ $fsurl.'rt/Approvals/', '' ],
;
$tools_ticketing{'Cron Tool'} = [ $fsurl.'rt/Developer/CronTool/', '' ]
if $conf->exists('rt-crontool');
@@ -453,6 +475,7 @@ tie my %config_radius, 'Tie::IxHash',
tie my %config_export_svc, 'Tie::IxHash', ();
if ( $curuser->access_right('Configuration') ) {
$config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
+ $config_export_svc{'Service classes'} = [ $fsurl.'browse/part_svc_class.html', 'Services classes are user-defined, informational types for services' ];
$config_export_svc{'Provisioning exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
}
$config_export_svc{'Dialup'} = [ \%config_dialup, '' ]
@@ -506,6 +529,10 @@ tie my %config_agent, 'Tie::IxHash',
'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ];
;
+tie my %config_sales, 'Tie::IxHash',
+ 'Sales' => [ $fsurl.'browse/sales.cgi', 'Sales bring in new business.' ],
+;
+
tie my %config_billing_rates, 'Tie::IxHash',
'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
@@ -541,12 +568,44 @@ if ( $curuser->access_right('Configuration') ) {
$config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ];
}
+#XXX also to be unified
+tie my %config_ticketing_groups, 'Tie::IxHash',
+ 'Select' => [ $fsurl.'rt/Admin/Groups', '' ],
+ 'Create' => [ $fsurl.'rt/Admin/Groups/Modify.html?Create=1', '' ],
+;
+
+tie my %config_ticketing_queues, 'Tie::IxHash',
+ 'Select' => [ $fsurl.'rt/Admin/Queues', '' ],
+ 'Create' => [ $fsurl.'rt/Admin/Queues/Modify.html?Create=1', '' ],
+;
+
+tie my %config_ticketing_customfields, 'Tie::IxHash',
+ 'Select' => [ $fsurl.'rt/Admin/CustomFields', '' ],
+ 'Create' => [ $fsurl.'rt/Admin/CustomFields/Modify.html?Create=1', '' ],
+;
+
+tie my %config_ticketing_articles_classes, 'Tie::IxHash',
+ 'Select' => [ $fsurl.'rt/Admin/Articles/Classes/', '' ],
+ 'Create' => [ $fsurl.'rt/Admin/Articles/Classes/Modify.html?Create=1', '' ],
+;
+
+tie my %config_ticketing_articles_customfields, 'Tie::IxHash',
+ 'Select' => [ $fsurl.'rt/Admin/CustomFields/index.html?type=RT%3A%3AClass-RT%3A%3AArticle', '' ],
+ 'Create' => [ $fsurl.'rt/Admin/CustomFields/Modify.html?Create=1&LookupType=RT%3A%3AClass-RT%3A%3AArticle', '' ],
+;
+
+tie my %config_ticketing_articles, 'Tie::IxHash',
+ 'Classes' => [ \%config_ticketing_articles_classes, '' ],
+ 'Custom Fields' => [ \%config_ticketing_articles_customfields, '' ],
+;
+
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' ],
+ 'Ticketing Users' => [ $fsurl.'rt/Admin/Users', 'Edit ticketing users' ], #XXX to be unified
+ 'Ticketing Groups' => [ \%config_ticketing_groups, 'View/Edit ticketing groups and group membership' ], #XXX to be unified
+ 'Ticketing Queues' => [ \%config_ticketing_queues, 'View/Edit ticketing queues and queue-specific properties' ],
+ 'Ticket Custom Fields' => [ \%config_ticketing_customfields, 'View/Edit ticketing custom fields' ],
+ 'Ticketing Global' => [ $fsurl.'rt/Admin/Global', 'View/Edit ticketing configuration applicable to all queues' ], #XXX the individual items
+ 'Ticketing Articles' => [ \%config_ticketing_articles, '' ],
#"System Configuraiton"? useless, just makes people report errors about missing Module::Versions::Report #'Ticketing Tools' => [ $fsurl.'rt/Admin/Tools', '' ],
;
@@ -556,9 +615,8 @@ tie my %config_nms, 'Tie::IxHash',
tie my %config_misc, 'Tie::IxHash';
$config_misc{'Message templates'} = [ $fsurl.'browse/msg_template.html', 'Templates for customer notices' ]
- if $curuser->access_right('Edit templates')
- || $curuser->access_right('Edit global templates')
- || $curuser->access_right('Configuration');
+ if $curuser->access_right(['View templates', 'View global templates',
+ 'Edit templates', 'Edit global templates', ]);
$config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.' ]
if $curuser->access_right('Edit advertising sources')
|| $curuser->access_right('Edit global advertising sources');
@@ -571,6 +629,9 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla
|| $curuser->access_right('Edit global inventory')
|| $curuser->access_right('Configuration');
+$config_misc{'FTP targets'} = [ $fsurl.'browse/ftp_target.html', 'FTP servers for billing and payment processing' ]
+ if $curuser->access_right('Configuration');
+
tie my %config_menu, 'Tie::IxHash';
if ( $curuser->access_right('Configuration' ) ) {
%config_menu = (
@@ -578,6 +639,7 @@ if ( $curuser->access_right('Configuration' ) ) {
'separator' => '', #its a separator!
'Employees' => [ \%config_employees, '' ],
'Resellers' => [ \%config_agent, '' ],
+ 'Sales People' => [ \%config_sales, '' ],
'separator2' => '', #its a separator!
'Customers' => [ \%config_cust, '' ],
#or this? 'Customers and Contacts' => [ \%config_cust, '' ],
@@ -622,18 +684,18 @@ my $doc_link = $conf->config('support-key')
eval "use RT;"
if $conf->config('ticket_system') eq 'RT_Internal';
-tie my %help_menu, 'Tie::IxHash', 'Billing documentation' => [ $doc_link, 'Freeside documentation' ];
-$help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Request Tracker Wiki' ]
- if $conf->config('ticket_system') eq 'RT_Internal';
-$help_menu{'Networking monitoring documentation'} = [ 'http://torrus.org/userguide.pod.html', 'Torrus User Guide' ]
- if $conf->config('network_monitoring_system') eq 'Torrus_Internal';
-$help_menu{'separator'} = '';
-
+tie my %help_menu, 'Tie::IxHash';
my $agentnum = $conf->config('brand-agent');
if ( $agentnum ) {
my $company_name = $conf->config('company_name', $agentnum);
$help_menu{"About $company_name"} = [ "javascript:about_freeside()", '' ];
} else {
+ $help_menu{'Billing documentation'} = [ $doc_link, 'Freeside documentation' ];
+ $help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Request Tracker Wiki' ]
+ if $conf->config('ticket_system') eq 'RT_Internal';
+ $help_menu{'Networking monitoring documentation'} = [ 'http://torrus.org/userguide.pod.html', 'Torrus User Guide' ]
+ if $conf->config('network_monitoring_system') eq 'Torrus_Internal';
+ $help_menu{'separator'} = '';
$help_menu{"About Freeside v$FS::VERSION"} = [ "javascript:about_freeside()", '' ];
$help_menu{"About RT v$RT::VERSION"} = [ 'http://www.bestpractical.com/rt', 'Request Tracker Homepage' ]
if $conf->config('ticket_system') eq 'RT_Internal';
diff --git a/httemplate/elements/order_pkg_link.html b/httemplate/elements/order_pkg_link.html
new file mode 100644
index 000000000..d8aa8fd4e
--- /dev/null
+++ b/httemplate/elements/order_pkg_link.html
@@ -0,0 +1,26 @@
+<& /elements/popup_link-cust_main.html,
+ 'action' => $p. 'misc/order_pkg.html',
+ 'label' => $opt{'label'} || emt('Order new package'),
+ 'actionlabel' => $opt{'actionlabel'} || emt('Order new package'),
+ 'color' => '#333399',
+ 'cust_main' => $opt{cust_main},
+ 'prospect_main' => $opt{prospect_main},
+ 'custnum' => $opt{custnum},
+ 'prospectnum' => $opt{prospectnum},
+ 'closetext' => emt('Close'),
+ 'width' => 960, #763,
+ 'height' => $height,
+ %optional,
+&>
+<%init>
+
+my(%opt) = @_;
+
+my %optional =
+ map { $_ => $opt{$_} }
+ grep $opt{$_},
+ qw( lock_pkgpart lock_locationnum qualnum quotationnum svcpart );
+
+my $height = $opt{'lock_locationnum'} ? 336 : 576;
+
+</%init>
diff --git a/httemplate/elements/popup_link-cust_main.html b/httemplate/elements/popup_link-cust_main.html
index 14137859f..541bb5837 100644
--- a/httemplate/elements/popup_link-cust_main.html
+++ b/httemplate/elements/popup_link-cust_main.html
@@ -22,7 +22,7 @@ Example:
)
</%doc>
-% if ( $params->{'cust_main'} ) {
+% if ( $custnum || $prospectnum ) {
<% include('/elements/popup_link.html', $params ) %>\
% }
<%init>
@@ -34,13 +34,26 @@ if (ref($_[0]) eq 'HASH') {
} else {
$params = { %$params, @_ };
}
+
+my $custnum = $params->{'cust_main'}
+ ? $params->{'cust_main'}->custnum
+ : $params->{'custnum'};
+
+$params->{'action'} .= ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
+ "custnum=$custnum"
+ if $custnum;
+
+#(maybe i should be called popup_link-cust_or_prospect_main.html now)
+my $prospectnum = $params->{'prospect_main'}
+ ? $params->{'prospect_main'}->prospectnum
+ : $params->{'prospectnum'};
-$params->{'action'} .=
- ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
- 'custnum='. $params->{'cust_main'}->custnum;
+$params->{'action'} .= ( $params->{'action'} =~ /\?/ ? ';' : '?' ).
+ "prospectnum=$prospectnum"
+ if $prospectnum;
$params->{'action'} .= ";$_=".$params->{$_}
foreach grep $params->{$_},
- qw( lock_pkgpart lock_locationnum qualnum svcpart );
+ qw( lock_pkgpart lock_locationnum qualnum quotationnum svcpart );
</%init>
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
index 2ec248e32..7a282a34c 100644
--- a/httemplate/elements/progress-init.html
+++ b/httemplate/elements/progress-init.html
@@ -54,10 +54,7 @@ sub process_whatever { #class method
)
%>
-% if (!$noinit) {
<& /elements/init_overlib.html &>
-% $noinit = 1;
-% }
<SCRIPT TYPE="text/javascript">
@@ -117,9 +114,6 @@ function <%$key%>myCallback( jobnum ) {
</SCRIPT>
-<%once>
-my $noinit = 0;
-</%once>
<%init>
my( $formname, $fields, $action, $url_or_message, $key ) = @_;
diff --git a/httemplate/elements/searchbar-ticket.html b/httemplate/elements/searchbar-ticket.html
index 774ca9875..30624f7d3 100644
--- a/httemplate/elements/searchbar-ticket.html
+++ b/httemplate/elements/searchbar-ticket.html
@@ -2,7 +2,7 @@
<FORM ACTION="<% FS::TicketSystem->baseurl %>index.html" METHOD="GET" STYLE="margin:0">
<INPUT NAME="q" TYPE="text" VALUE="<% $ticketing_label |n %>" STYLE="width:<% $width %>" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" CLASS="fstext"><BR>
- <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" CLASS="fslink" STYLE="font-size:11px"><% mt('Advanced') |h %></A>
+ <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html?NewQuery=1" CLASS="fslink" STYLE="font-size:11px"><% mt('Advanced') |h %></A>
<INPUT TYPE="submit" VALUE="<% mt('Search tickets') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
</FORM>
<% $menu_position eq 'left' ? '<BR>' : '' %>
diff --git a/httemplate/elements/select-cust-part_pkg.html b/httemplate/elements/select-cust-part_pkg.html
index 731beae2a..2d4cd183d 100644
--- a/httemplate/elements/select-cust-part_pkg.html
+++ b/httemplate/elements/select-cust-part_pkg.html
@@ -27,9 +27,9 @@ Example:
my( %opt ) = @_;
-my $cust_main = $opt{'cust_main'}
- or die "cust_main not specified";
+my $cust_or_prospect_main = $opt{'cust_main'} || $opt{'prospect_main'}
+ or die "neither cust_main nor prospect_main specified";
-$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent );
+$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_or_prospect_main->agent );
</%init>
diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html
index ec37eaf67..2114c07a1 100644
--- a/httemplate/elements/select-cust_pkg-status.html
+++ b/httemplate/elements/select-cust_pkg-status.html
@@ -3,7 +3,9 @@
<% $onchange %>
>
+% if ( !$opt{'disable_empty'} ) {
<OPTION VALUE="">all
+% }
% foreach my $option ( @{ $opt{'statuses'} } ) {
diff --git a/httemplate/elements/select-part_svc_class.html b/httemplate/elements/select-part_svc_class.html
new file mode 100644
index 000000000..280e3e17d
--- /dev/null
+++ b/httemplate/elements/select-part_svc_class.html
@@ -0,0 +1,22 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'part_svc_class',
+ 'name_col' => 'classname',
+ 'value' => $classnum,
+ 'empty_label' => '(none)',
+ 'hashref' => \%hash,
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+my %hash = ();
+$hash{'disabled'} = '' unless $opt{'showdisabled'};
+
+
+$opt{'records'} = delete $opt{'part_svc_class'}
+ if $opt{'part_svc_class'};
+
+</%init>
diff --git a/httemplate/elements/select-rt-customfield.html b/httemplate/elements/select-rt-customfield.html
index 7a45bb14b..85758d585 100644
--- a/httemplate/elements/select-rt-customfield.html
+++ b/httemplate/elements/select-rt-customfield.html
@@ -3,9 +3,6 @@
<OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION>
% }
</SELECT>
-<%once>
-RT::Init();
-</%once>
<%init>
my %opt = @_;
my $lookuptype = $opt{lookuptype};
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
index c0dde7414..c0cd7a50b 100644
--- a/httemplate/elements/select-table.html
+++ b/httemplate/elements/select-table.html
@@ -93,10 +93,17 @@ Example:
% )
% {
% my $recvalue = $record->$key();
+% my $selected;
+% if ( $opt{'all_selected'} ) {
+% $selected = 1;
+% } elsif ( $opt{'compare_sub'} && !ref($value) ) {
+% $selected = &{ $opt{'compare_sub'} }( $value, $recvalue );
+% } else {
+% $selected = ( ref($value) && $value->{$recvalue} )
+% || ( $value && $value eq $recvalue ); #not == because of value_col
+% }
<OPTION VALUE="<% $recvalue %>"
- <% $opt{'all_selected'} || ref($value) && $value->{$recvalue} || $value && $value eq $recvalue # not == because of value_col
- ? ' SELECTED' : ''
- %>
+ <% $selected ? ' SELECTED' : '' %>
% foreach my $att ( @{ $opt{'extra_option_attributes'} } ) {
data-<% $att %>="<% $record->$att() |h %>"
% }
@@ -174,24 +181,29 @@ if ( $opt{'records'} ) {
});
}
-unless ( $value < 1 # !$value #ignore negatives too
- or ref($value)
+if ( ref( $value ) eq 'ARRAY' ) {
+ $value = { map { $_ => 1 } @$value };
+}
+
+unless ( !ref($value) && $value < 1 # !$value #ignore negatives too
or ! exists( $opt{hashref}->{disabled} ) #??
- or grep { $value == $_->$key() } @records
+ #or grep { $value == $_->$key() } @records
) {
delete $opt{hashref}->{disabled};
- $opt{hashref}->{$key} = $value;
- my $record = qsearchs( {
- 'table' => $opt{table},
- 'addl_from' => $opt{'addl_from'},
- 'hashref' => $hashref,
- 'extra_sql' => $extra_sql,
- });
- push @records, $record if $record;
-}
-if ( ref( $value ) eq 'ARRAY' ) {
- $value = { map { $_ => 1 } @$value };
+ foreach my $v ( ref($value) ? keys %$value : ($value) ) {
+ next if grep { $v == $_->$key() } @records;
+
+ $opt{hashref}->{$key} = $v;
+ my $record = qsearchs( {
+ 'table' => $opt{table},
+ 'addl_from' => $opt{'addl_from'},
+ 'hashref' => $hashref,
+ 'extra_sql' => $extra_sql,
+ });
+ push @records, $record if $record;
+
+ }
}
my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index ee4c5e7f3..f6564a55e 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -7,7 +7,7 @@ function form_address_info() {
'onlyship': 1,
% } else {
% if ( $withfirm ) {
- 'company': cf.elements['<% $main_prefix %>company'].value,
+ 'company', cf.elements['company'].value,
% }
'address1': cf.elements['<% $main_prefix %>address1'].value,
'address2': cf.elements['<% $main_prefix %>address2'].value,
@@ -16,9 +16,6 @@ function form_address_info() {
'zip': cf.elements['<% $main_prefix %>zip'].value,
'country': cf.elements['<% $main_prefix %>country'].value,
% }
-% if ( $withfirm ) {
- 'ship_company': cf.elements['<% $ship_prefix %>company'].value,
-% }
% if ( $withcensus ) {
'ship_censustract': cf.elements['enter_censustract'].value,
% }
diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html
new file mode 100644
index 000000000..6d1a45a0d
--- /dev/null
+++ b/httemplate/elements/table-tickets.html
@@ -0,0 +1,159 @@
+<& /elements/form-create_ticket.html, object => $object &>
+ |
+View
+<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> |
+<A HREF="<% $res_link %>"><% mt('resolved') |h %></A>
+ <BR>
+
+<& /elements/table-grid.html &>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Queue') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Owner') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Due') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Estimated Time') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Priority') |h %></TH>
+% if ( $ss_priority ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Customer Priority') |h %></TH>
+% }
+% if ( $object->isa('FS::cust_main') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Service') |h %></TH>
+% }
+</TR>
+
+% foreach my $ticket ( @tickets ) {
+% my $href = FS::TicketSystem->href_ticket($ticket->{id});
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{id} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF=<%$href%>><% $ticket->{subject} %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{status} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{queue} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{owner} %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $date_formatter->($ticket->{due}) %>
+ </TD>
+
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{timeestimated} %>
+ </TD>
+
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{content}
+ ? $ticket->{content}.' ('.$ticket->{priority}.')'
+ : $ticket->{priority}
+ %>
+ </TD>
+
+% if ( $ss_priority ) {
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{"CF.{$ss_priority}"} %>
+ </TD>
+% }
+% if ( $object->isa('FS::cust_main') ) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE=-1><B>
+% foreach (@{ $ticket->{svcnums} }) {
+% my $cust_svc = FS::cust_svc->by_key($_) or next;
+ <% FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) %>
+ <BR>
+% }
+ </B></FONT></TD>
+% }
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<%init>
+use Date::Parse qw(str2time);
+use Date::Format qw(time2str);
+
+my %opt = @_;
+my $conf = new FS::Conf;
+
+my $object = $opt{'object'};
+$object = $object->cust_svc if $object->isa('FS::svc_Common');
+my( @tickets ) = $object->tickets;
+
+my ($openlabel, $open_link, $res_link, $thing);
+$openlabel = join('/', FS::TicketSystem->statuses );
+
+# not the nicest way to do this--FS::has_tickets_Common?
+if ( $object->isa('FS::cust_main') ) {
+ $thing = 'customer';
+ $open_link = FS::TicketSystem->href_customer_tickets($object->custnum);
+
+ $res_link = FS::TicketSystem->href_customer_tickets(
+ $object->custnum,
+ { 'statuses' => [ 'resolved' ] }
+ );
+}
+elsif ( $object->isa('FS::cust_svc') ) {
+ $thing = 'service';
+ $open_link = FS::TicketSystem->href_service_tickets($object->svcnum);
+
+ $res_link = FS::TicketSystem->href_service_tickets(
+ $object->svcnum,
+ { 'statuses' => [ 'resolved' ] }
+ );
+}
+
+my $ss_priority = FS::TicketSystem->selfservice_priority;
+if ( $ss_priority ) {
+ my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1;
+ use sort 'stable';
+ # sort in the following way:
+ @tickets = sort {
+ # within a severity level...
+ ( $a->{'content'} eq $b->{'content'} ) ? (
+ # no-priority tickets sort last
+ (
+ ($a->{'_selfservice_priority'} eq '') <=>
+ ($b->{'_selfservice_priority'} eq '')
+ ) ||
+ # otherwise obey ticket_system-priority_reverse
+ ( $dir *
+ ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'})
+ )
+ ) : 0; # but don't rearrange between severity levels
+ } @tickets;
+}
+
+my $format = $conf->config('date_format') || '%Y-%m-%d';
+
+my $date_formatter = sub {
+ my $time = str2time($_[0], 'GMT');
+ # exclude times within 24 hours of zero
+ ($time > 86400) ? time2str($format, $time) : '';
+};
+
+</%init>
diff --git a/httemplate/elements/tr-amount_fee.html b/httemplate/elements/tr-amount_fee.html
new file mode 100644
index 000000000..12488521a
--- /dev/null
+++ b/httemplate/elements/tr-amount_fee.html
@@ -0,0 +1,100 @@
+ <TR>
+ <TH ALIGN="right"><% mt('Payment amount') |h %></TH>
+ <TD COLSPAN=7>
+ <TABLE><TR><TD BGCOLOR="#ffffff">
+ <% $money_char %><INPUT NAME = "amount"
+ ID = "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>
+
+% }
+
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $fee = '';
+my $fee_pkg = '';
+my $fee_display = '';
+my $fee_op = '';
+
+if ( $opt{'process-pkgpart'}
+ and ! $opt{'process-skip_first'} || $opt{'num_payments'}
+ )
+{
+
+ $fee_display = $opt{'process-display'} || 'add';
+ $fee_op = $fee_display eq 'add' ? '+' : '-';
+
+ $fee_pkg =
+ qsearchs('part_pkg', { pkgpart=>$opt{'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 = $opt{'amount'};
+if ( $amount > 0 ) {
+ $amount += $fee
+ if $fee && $fee_display eq 'subtract';
+
+ #&{ $opt{post_fee_callback} }( \$amount ) if $opt{post_fee_callback};
+ $amount += $amount * $opt{'surcharge_percentage'}/100
+ if $opt{'surcharge_percentage'} > 0;
+
+ $amount = sprintf("%.2f", $amount);
+}
+
+</%init>
diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html
index ca5de86b4..1ca22f6d4 100644
--- a/httemplate/elements/tr-cust_svc.html
+++ b/httemplate/elements/tr-cust_svc.html
@@ -24,7 +24,7 @@ Usage:
<TD ALIGN="right" VALIGN="top"><%
FS::UI::Web::svc_link($m, $part_svc, $cust_svc)
%></TD>
- <TD STYLE="padding-bottom:0px"><B><%
+ <TD STYLE="padding-bottom:0px"><B><% $cust_svc->agent_svcid ? $cust_svc->agent_svcid.': ' : '' %><%
FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc)
%></B></TD>
<TD ALIGN="right"><% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %>
diff --git a/httemplate/elements/tr-fixed-cust_main.html b/httemplate/elements/tr-fixed-cust_main.html
new file mode 100644
index 000000000..00bcb66d8
--- /dev/null
+++ b/httemplate/elements/tr-fixed-cust_main.html
@@ -0,0 +1,15 @@
+% if ( $cust_main ) {
+ <% include('tr-fixed.html', %opt ) %>
+% }
+<%init>
+
+my %opt = @_;
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $cust_main = $value ? qsearchs('cust_main', {custnum=>$value} )
+ : '';
+
+$opt{'formatted_value'} = $cust_main->name if $cust_main;
+
+</%init>
diff --git a/httemplate/elements/tr-fixed-date.html b/httemplate/elements/tr-fixed-date.html
new file mode 100644
index 000000000..716e5ceb8
--- /dev/null
+++ b/httemplate/elements/tr-fixed-date.html
@@ -0,0 +1,13 @@
+<% include('tr-fixed.html', %opt ) %>
+<%init>
+
+my %opt = @_;
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+$opt{'formatted_value'} = time2str($date_format, $value);
+
+</%init>
diff --git a/httemplate/elements/tr-fixed-prospect_main.html b/httemplate/elements/tr-fixed-prospect_main.html
new file mode 100644
index 000000000..8da0ffb84
--- /dev/null
+++ b/httemplate/elements/tr-fixed-prospect_main.html
@@ -0,0 +1,15 @@
+% if ( $prospect_main ) {
+ <% include('tr-fixed.html', %opt ) %>
+% }
+<%init>
+
+my %opt = @_;
+
+my $value = $opt{'curr_value'} || $opt{'value'};
+
+my $prospect_main = $value ? qsearchs('prospect_main', {prospectnum=>$value} )
+ : '';
+
+$opt{'formatted_value'} = $prospect_main->name if $prospect_main;
+
+</%init>
diff --git a/httemplate/elements/tr-fixed.html b/httemplate/elements/tr-fixed.html
index f358343dd..dd07d90b6 100644
--- a/httemplate/elements/tr-fixed.html
+++ b/httemplate/elements/tr-fixed.html
@@ -13,13 +13,15 @@ my %opt = @_;
my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
my $value = $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'};
-#compatibility with select-table and friends
-if ( $opt{'multiple'} ) {
- $value = [ split(/\s*,\s*/, $value) ] if !ref $value;
- $value = join('<BR>', map {encode_entities($_)} @$value);
-}
-else {
- $value = encode_entities($value)
+
+unless ( $opt{'noescape'} ) {
+ #compatibility with select-table and friends
+ if ( $opt{'multiple'} ) {
+ $value = [ split(/\s*,\s*/, $value) ] if !ref $value;
+ $value = join('<BR>', map {encode_entities($_)} @$value);
+ } else {
+ $value = encode_entities($value)
+ }
}
</%init>
diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html
index ce03c40f5..321bd6b32 100644
--- a/httemplate/elements/tr-select-agent.html
+++ b/httemplate/elements/tr-select-agent.html
@@ -22,7 +22,7 @@ Example:
);
</%doc>
-% if ( scalar(@agents) == 1 ) {
+% if ( scalar(@agents) == 1 || $opt{'fixed'} ) {
<INPUT TYPE = "hidden"
NAME = "<% $opt{'field'} || 'agentnum' %>"
@@ -30,9 +30,20 @@ Example:
VALUE = "<% $agents[0]->agentnum %>"
>
-%# YUCK. empty row so we don't throw g_row in edit.html off :/
- <TR>
- </TR>
+% if ( scalar(@agents) != 1 ) {
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || emt('Agent') %></TD>
+ <TD BGCOLOR="#dddddd" <% $colspan %>>
+% my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
+ <% $agent ? $agent->agent : '(all)' |h %>
+ </TD>
+ </TR>
+
+% } else { # YUCK. empty row so we don't throw g_row in edit.html off :/
+ <TR>
+ </TR>
+% }
+%
% } else {
<TR>
diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html
index 767d23264..848ab0a4b 100644
--- a/httemplate/elements/tr-select-cust-part_pkg.html
+++ b/httemplate/elements/tr-select-cust-part_pkg.html
@@ -51,8 +51,11 @@
}
- get_part_pkg( <% $cust_main->custnum %>, classnum, update_part_pkg );
-
+ get_part_pkg( <% $cust_main ? $cust_main->custnum : '0' %>,
+ <% $prospect_main ? $prospect_main->prospectnum : '0' %>,
+ classnum,
+ update_part_pkg
+ );
}
</SCRIPT>
@@ -74,10 +77,11 @@
<TH ALIGN="right"><% mt('Package') |h %></TH>
<TD COLSPAN=7>
<& /elements/select-cust-part_pkg.html,
- 'curr_value' => $opt{'curr_value'}, #$pkgpart
- 'classnum' => $opt{'classnum'},
- 'cust_main' => $opt{'cust_main'}, #$cust_main
- 'onchange' => 'pkg_changed',
+ 'curr_value' => $opt{'curr_value'}, #$pkgpart
+ 'classnum' => $opt{'classnum'},
+ 'cust_main' => $opt{'cust_main'}, #$cust_main
+ 'prospect_main' => $opt{'prospect_main'}, #$prospect_main
+ 'onchange' => 'pkg_changed',
&>
</TD>
</TR>
@@ -91,8 +95,13 @@ 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 $cust_main = $opt{'cust_main'};
+my $prospect_main = $opt{'prospect_main'};
+
+die "neither cust_main nor prospect_main specified"
+ unless $cust_main || $prospect_main;
+
+my $agent = $cust_main ? $cust_main->agent : $prospect_main->agent;
#"normal" part_pkg agent virtualization (agentnum or type)
my @part_pkg = qsearch({
@@ -101,7 +110,7 @@ my @part_pkg = qsearch({
'hashref' => { 'disabled' => '' },
'extra_sql' =>
' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
- ' AND '. FS::part_pkg->agent_pkgs_sql( $opt{'cust_main'}->agent ),
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $agent ),
});
my @pkg_class =
diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html
index 801152e9f..b472cc5a7 100644
--- a/httemplate/elements/tr-select-cust_location.html
+++ b/httemplate/elements/tr-select-cust_location.html
@@ -11,7 +11,6 @@ Example:
#optional
'empty_label' => '(default service address)',
- 'disable_empty' => 0, #1 to disable
)
</%doc>
@@ -52,12 +51,13 @@ Example:
var ftype = what.form.<%$_%>.tagName;
if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
% }
-
- if ( what.form.location_type &&
- what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
- what.form.location_number.disabled = false;
- what.form.location_number.style.backgroundColor = '#ffffff';
- }
+% if ( $opt{'alt_format'} ) {
+ if ( what.form.location_type &&
+ what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
+ what.form.location_number.disabled = false;
+ what.form.location_number.style.backgroundColor = '#ffffff';
+ }
+% }
}
function locationnum_changed(what) {
@@ -102,25 +102,8 @@ Example:
return;
}
- if ( locationnum == 0 ) { //(default service address)
-% if ( $cust_main ) {
- what.form.address1.value = <% $cust_main->get($prefix.'address1') |js_string %>;
- what.form.address2.value = <% $cust_main->get($prefix.'address2') |js_string %>;
- what.form.city.value = <% $cust_main->get($prefix.'city') |js_string %>;
- what.form.zip.value = <% $cust_main->get($prefix.'zip') |js_string %>;
-
- changeSelect(what.form.country, <% $cust_main->get($prefix.'country') | js_string %> );
-
- country_changed( what.form.country,
- fix_state_factory( <% $cust_main->get($prefix.'state') | js_string %>,
- <% $cust_main->get($prefix.'county') | js_string %>
- )
- );
-% }
-
- } else {
- get_location( locationnum, update_location );
- }
+%# default service address is now just another location
+ get_location( locationnum, update_location );
% if ( $editable ) {
if ( locationnum == 0 ) {
@@ -204,14 +187,16 @@ Example:
ID = "locationnum"
onChange = "locationnum_changed(this);"
>
-% if ( !$prospect_main && !$opt{'disable_empty'} ) {
- <OPTION VALUE=""><% $opt{'empty_label'} || '(default service address)' |h %>
+% if ( $cust_main ) {
+ <OPTION VALUE="<% $cust_main->ship_locationnum %>"><% $opt{'empty_label'} || '(default service address)' |h %>
% }
% if ( $opt{'is_optional'} ) {
<OPTION VALUE="-2" <% $locationnum == -2 ? 'SELECTED' : ''%>><% $opt{'optional_label'} || '(not required)' |h %>
% }
%
% foreach my $loc ( @cust_location ) {
+% # don't show the ship_location redundantly
+% next if $cust_main && $cust_main->ship_locationnum == $loc->locationnum;
<OPTION VALUE="<% $loc->locationnum %>"
<% $locationnum == $loc->locationnum ? 'SELECTED' : '' %>
><% $loc->line |h %>
@@ -232,9 +217,12 @@ Example:
'no_asterisks' => 1,
'no_bold' => $opt{'no_bold'},
'alt_format' => $opt{'alt_format'},
+ 'enable_coords'=> 1,
)
%>
-
+<SCRIPT TYPE="text/javascript">
+ locationnum_changed(document.getElementById('locationnum'));
+</SCRIPT>
<%init>
my $conf = new FS::Conf;
@@ -247,8 +235,7 @@ my $cgi = $opt{'cgi'};
my $cust_pkg = $opt{'cust_pkg'};
my $cust_main = $opt{'cust_main'};
my $prospect_main = $opt{'prospect_main'};
-
-my $prefix = ($cust_main && length($cust_main->ship_last)) ? 'ship_' : '';
+die "cust_main or prospect_main required" unless $cust_main or $prospect_main;
my $locationnum = '';
if ( $cgi->param('error') ) {
@@ -260,9 +247,9 @@ if ( $cgi->param('error') ) {
} elsif ($prospect_main) {
my @cust_location = $prospect_main->cust_location;
$locationnum = $cust_location[0]->locationnum if scalar(@cust_location)==1;
- } else { #?
+ } else { #$cust_main
$cgi->param('locationnum') =~ /^(\-?\d*)$/ or die "illegal locationnum";
- $locationnum = $1;
+ $locationnum = $1 || $cust_main->ship_locationnum;
}
}
@@ -278,7 +265,7 @@ if ( $opt{'alt_format'} ) {
push @location_fields, qw( location_type location_number location_kind );
}
-my $cust_location;
+my $cust_location; #the one that shows by default in the location edit space
if ( $locationnum && $locationnum > 0 ) {
$cust_location = qsearchs('cust_location', { 'locationnum' => $locationnum } )
or die "unknown locationnum";
@@ -291,7 +278,7 @@ if ( $locationnum && $locationnum > 0 ) {
$cust_location->$_( $pkg_location->$_ ) foreach @location_fields;
$opt{'empty_label'} ||= 'package address: '.$pkg_location->line;
} elsif ( $cust_main ) {
- $cust_location->$_( $cust_main->get($prefix.$_) ) foreach @location_fields;
+ $cust_location = $cust_main->ship_location; #I think
}
}
@@ -304,7 +291,7 @@ my $location_sort = sub {
or lc($a->address2) cmp lc($b->address2)
};
-my @cust_location = ();
+my @cust_location;
push @cust_location, $cust_main->cust_location if $cust_main;
push @cust_location, $prospect_main->cust_location if $prospect_main;
push @cust_location, $cust_location
@@ -314,14 +301,14 @@ push @cust_location, $cust_location
@cust_location = sort $location_sort grep !$_->disabled, @cust_location;
$cust_location = $cust_location[0]
- if ( $prospect_main || $opt{'disable_empty'} )
+ if ( $prospect_main )
&& !$opt{'is_optional'}
&& @cust_location;
my $disabled =
( $locationnum < 0
|| ( $editable && $locationnum )
- || ( ( $prospect_main || $opt{'disable_empty'} )
+ || ( $prospect_main
&& !$opt{'is_optional'} && !@cust_location && $addnew
)
)
diff --git a/httemplate/elements/tr-select-discount.html b/httemplate/elements/tr-select-discount.html
index 30a60ec85..ee862519f 100644
--- a/httemplate/elements/tr-select-discount.html
+++ b/httemplate/elements/tr-select-discount.html
@@ -6,7 +6,7 @@
% } else {
<TR>
- <TD ALIGN="right" WIDTH="176"><% $opt{'label'} || '<B>'.emt('Discount').'</B>' %></TD>
+ <TD ALIGN="right" WIDTH="275"><% $opt{'label'} || '<B>'.emt('Discount').'</B>' %></TD>
<TD <% $colspan %>>
<% include( '/elements/select-discount.html',
'curr_value' => $discountnum,
@@ -74,6 +74,16 @@
)
%>
+%# <% include( '/elements/tr-checkbox.html',
+%# 'label' => '<B>Apply discount to add-on packages</B>',
+%# 'field' => $name.'_linked',
+%# 'id' => $name.'_linked',
+%# 'curr_value' => scalar($cgi->param($name.'_linked')),
+%# 'value' => 'Y',
+%# 'colspan' => $opt{'colspan'},
+%# )
+%# %>
+
<SCRIPT TYPE="text/javascript">
% my $ge = 'document.getElementById';
@@ -136,6 +146,10 @@
<% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden';
<% $ge %>('<% $name %>_percent_input0').style.display = 'none';
<% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+// <% $ge %>('<% $name %>_linked_label0').style.display = 'none';
+// <% $ge %>('<% $name %>_linked_label0').style.visibility = 'hidden';
+// <% $ge %>('<% $name %>_linked').style.display = 'none';
+// <% $ge %>('<% $name %>_linked').style.visibility = 'hidden';
} else if ( <% $name %>__type == 'Amount' ) {
<% $ge %>('<% $name %>_amount_label0').style.display = '';
<% $ge %>('<% $name %>_amount_label0').style.visibility = '';
@@ -145,6 +159,11 @@
<% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden';
<% $ge %>('<% $name %>_percent_input0').style.display = 'none';
<% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden';
+// <% $ge %>('<% $name %>_linked_label0').style.display = 'none';
+// <% $ge %>('<% $name %>_linked_label0').style.visibility = 'hidden';
+// <% $ge %>('<% $name %>_linked').style.display = 'none';
+// <% $ge %>('<% $name %>_linked').style.visibility = 'hidden';
} else if ( <% $name %>__type == 'Percentage' ) {
<% $ge %>('<% $name %>_amount_label0').style.display = 'none';
<% $ge %>('<% $name %>_amount_label0').style.visibility = 'hidden';
@@ -154,6 +173,11 @@
<% $ge %>('<% $name %>_percent_label0').style.visibility = '';
<% $ge %>('<% $name %>_percent_input0').style.display = '';
<% $ge %>('<% $name %>_percent_input0').style.visibility = '';
+ <% $ge %>('<% $name %>_percent_input0').style.visibility = '';
+// <% $ge %>('<% $name %>_linked_label0').style.display = '';
+// <% $ge %>('<% $name %>_linked_label0').style.visibility = '';
+// <% $ge %>('<% $name %>_linked').style.display = '';
+// <% $ge %>('<% $name %>_linked').style.visibility = '';
}
}
diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html
index 765aa8400..5041f7f73 100644
--- a/httemplate/elements/tr-select-part_referral.html
+++ b/httemplate/elements/tr-select-part_referral.html
@@ -14,13 +14,7 @@
<INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'refnum' %>" VALUE="<% $opt{'part_referrals'}->[0]->refnum %>">
% } else {
-
- <TR>
-% if ( $opt{'label'} ) {
- <TD ALIGN="right"><% $opt{'label'} %></TD>
-% } else {
- <TH ALIGN="right"><%$r%><% mt('Advertising source') |h %></TH>
-% }
+ <& /elements/tr-td-label.html, label => 'Advertising source', %opt &>
<TD COLSPAN="<% $colspan %>">
<& /elements/select-part_referral.html,
'curr_value' => $refnum,
diff --git a/httemplate/elements/tr-select-part_svc_class.html b/httemplate/elements/tr-select-part_svc_class.html
new file mode 100644
index 000000000..2f4b09381
--- /dev/null
+++ b/httemplate/elements/tr-select-part_svc_class.html
@@ -0,0 +1,27 @@
+% if ( scalar(@{ $opt{'part_svc_class'} }) == 0 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Service class' %></TD>
+ <TD>
+ <% include( '/elements/select-part_svc_class.html',
+ 'curr_value' => $classnum,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'part_svc_class'} ||= [ qsearch( 'part_svc_class', { disabled=>'' } ) ];
+
+</%init>
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
index 5a79d68ef..c1df10b94 100755
--- a/httemplate/elements/tr-select-reason.html
+++ b/httemplate/elements/tr-select-reason.html
@@ -32,8 +32,15 @@ Example:
<SCRIPT TYPE="text/javascript">
function sh_add<% $func_suffix %>()
{
+ var hints = <% encode_json(\@hints) %>;
+ var select_reason = document.getElementById('<% $id %>');
- if (document.getElementById('<% $id %>').selectedIndex == 0){
+% if ( $class eq 'S' ) {
+ document.getElementById('<% $id %>_hint').innerHTML =
+ hints[select_reason.selectedIndex];
+% }
+
+ if (select_reason.selectedIndex == 0){
<% $controlledbutton ? $controlledbutton.'.disabled = true;' : ';' %>
}else{
<% $controlledbutton ? $controlledbutton.'.disabled = false;' : ';' %>
@@ -41,8 +48,8 @@ Example:
%if ($curuser->access_right($add_access_right)){
- if (document.getElementById('<% $id %>').selectedIndex ==
- (document.getElementById('<% $id %>').length - 1)) {
+ if (select_reason.selectedIndex ==
+ (select_reason.length - 1)) {
document.getElementById('new<% $id %>').disabled = false;
document.getElementById('new<% $id %>').style.display = 'inline';
document.getElementById('new<% $id %>Label').style.display = 'inline';
@@ -113,6 +120,13 @@ Example:
</TR>
% }
+% if ( $class eq 'S' ) {
+<TR>
+ <TD COLSPAN=2 ALIGN="center" id="<% $id %>_hint">
+ </TD>
+</TR>
+% }
+
<TR>
<TD ALIGN="right">
<P id="new<% $id %>Label" style="display:<% $display %>"><% mt('New Reason') |h %></P>
@@ -184,6 +198,43 @@ my @reasons = qsearch({
order_by => 'ORDER BY reason_type.type ASC, reason.reason ASC',
});
+my @hints;
+if ( $class eq 'S' ) {
+ my $conf = FS::Conf->new;
+ @hints = ( '' );
+ foreach my $reason (@reasons) {
+ if ( $reason->unsuspend_pkgpart ) {
+ my $part_pkg = FS::part_pkg->by_key($reason->unsuspend_pkgpart);
+ if ( $part_pkg ) {
+ if ( $part_pkg->option('setup_fee',1) > 0 and
+ $part_pkg->option('recur_fee',1) == 0 ) {
+ # the usual case
+ push @hints,
+ mt('A [_1] unsuspension fee will apply.',
+ ($conf->config('money_char') || '$') .
+ sprintf('%.2f', $part_pkg->option('setup_fee'))
+ );
+ } else {
+ # oddball cases--not really supported
+ push @hints,
+ mt('An unsuspension package will apply: [_1]',
+ $part_pkg->price_info
+ );
+ }
+ } else { #no $part_pkg
+ push @hints,
+ '<FONT COLOR="#ff0000">Unsuspend pkg #'.$reason->unsuspend_pkgpart.
+ ' not found.</FONT>';
+ }
+ } else { #no unsuspend_pkgpart
+ push @hints, '';
+ }
+ }
+ push @hints, ''; # for the "new reason" case
+ @hints = map {'<FONT SIZE="-1">'.$_.'</FONT>'} @hints;
+}
+
+
my $curuser = $FS::CurrentUser::CurrentUser;
</%init>
diff --git a/httemplate/elements/tr-select-voip_class.html b/httemplate/elements/tr-select-voip_class.html
new file mode 100644
index 000000000..dcc1487cc
--- /dev/null
+++ b/httemplate/elements/tr-select-voip_class.html
@@ -0,0 +1,24 @@
+<& tr-td-label.html, label => 'Category', @_ &>
+<TD>
+<SELECT NAME="<% $opt{'field'} %>">
+% while(@options) {
+% my $value = shift @options;
+% my $selected = ($value eq $opt{'curr_value'}) ? 'SELECTED' : '';
+ <OPTION VALUE="<% $value %>" <% $selected %>><% shift @options %></OPTION>
+% }
+</SELECT>
+</TD></TR>
+<%init>
+my %opt = (
+ field => 'fcc_voip_class',
+ label => 'Category',
+ @_
+);
+my @options = (
+ '' => '',
+ 1 => 'VoIP without Broadband',
+ 2 => 'VoIP with Broadband',
+ 3 => 'Wholesale VoIP'
+);
+
+</%init>
diff --git a/httemplate/elements/tr-svc_export_machine.html b/httemplate/elements/tr-svc_export_machine.html
new file mode 100644
index 000000000..92b6ac1d7
--- /dev/null
+++ b/httemplate/elements/tr-svc_export_machine.html
@@ -0,0 +1,37 @@
+% foreach my $part_export (@part_export) {
+% my $label = ( $part_export->exportname
+% ? $part_export->exportname
+% : $part_export->label
+% ).
+% ' hostname';
+%
+% my $element = 'exportnum'. $part_export->exportnum. 'machinenum';
+% my $machinenum = $opt{cgi}->param($element);
+% if ( ! $machinenum && $opt{svc}->svcnum ) {
+% my $svc_export_machine = qsearchs('svc_export_machine', {
+% 'svcnum' => $opt{svc}->svcnum,
+% 'exportnum' => $part_export->exportnum,
+% });
+% $machinenum = $svc_export_machine->machinenum if $svc_export_machine;
+% }
+
+ <& /elements/tr-select-table.html,
+ 'label' => $label,
+ 'element_name' => 'exportnum'. $part_export->exportnum. 'machinenum',
+ 'table' => 'part_export_machine',
+ 'name_col' => 'machine',
+ 'hashref' => { 'exportnum' => $part_export->exportnum,
+ 'disabled' => '',
+ },
+ 'curr_value' => $machinenum,
+ 'empty_label' => 'Select export hostname',
+ &>
+% }
+<%init>
+
+my %opt = @_;
+
+my @part_export = grep { $_->machine eq '_SVC_MACHINE' }
+ $opt{part_svc}->part_export;
+
+</%init>
diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css
index 73699b6f9..e6a39f675 100644
--- a/httemplate/elements/xmenu.css
+++ b/httemplate/elements/xmenu.css
@@ -20,7 +20,7 @@
.webfx-menu {
position: absolute;
- z-index: 100;
+ z-index: 9999;
visibility: hidden;
border: 1px solid #7e0079;
-moz-border-radius: 4px;
diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css
index ff0d6f684..7a42254e4 100644
--- a/httemplate/elements/xmenu.top.css
+++ b/httemplate/elements/xmenu.top.css
@@ -20,7 +20,7 @@
.webfx-menu {
position: absolute;
- z-index: 100;
+ z-index: 9999;
visibility: hidden;
border: 1px solid #7e0079;
-moz-border-radius: 4px;
diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi
index af4d045fe..c334ae9e7 100644
--- a/httemplate/graph/cust_bill_pkg.cgi
+++ b/httemplate/graph/cust_bill_pkg.cgi
@@ -8,6 +8,7 @@
'graph_labels' => \@labels,
'colors' => \@colors,
'links' => \@links,
+ 'no_graph' => \@no_graph,
'remove_empty' => 1,
'bottom_total' => 1,
'bottom_link' => $bottom_link,
@@ -49,6 +50,21 @@ elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
die "agentnum $agentnum not found!" unless $sel_agent;
}
my $title = $sel_agent ? $sel_agent->agent.' ' : '';
+
+my( $refnum, $sel_part_referral, $all_part_referral ) = ('', '', '');
+if ( $cgi->param('refnum') eq 'all' ) {
+ $refnum = 0;
+ $all_part_referral = 'ALL';
+}
+elsif ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ $refnum = $1;
+ $bottom_link .= "refnum=$refnum;";
+ $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } );
+ die "part_referral $refnum not found!" unless $sel_part_referral;
+}
+$title .= $sel_part_referral->referral.' '
+ if $sel_part_referral;
+
$title .= 'Sales Report (Gross)';
$title .= ', average per customer package' if $average_per_cust_pkg;
@@ -103,6 +119,7 @@ my @params = ();
my @labels = ();
my @colors = ();
my @links = ();
+my @no_graph;
my @components = ( 'SRU' );
# split/omit components as appropriate
@@ -119,6 +136,11 @@ elsif ( $use_usage == 2 ) {
$components[-1] =~ s/U//;
}
+# Categorization of line items goes
+# Agent -> Referral -> Package class -> Component (setup/recur/usage)
+# If per-agent totals are enabled, they go under the Agent level.
+# There aren't any other kinds of subtotals.
+
foreach my $agent ( $all_agent || $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) {
my $col_scheme = Color::Scheme->new
@@ -130,43 +152,91 @@ foreach my $agent ( $all_agent || $sel_agent || qsearch('agent', { 'disabled' =>
### fixup the color handling for package classes...
### and usage
- my $n = 0;
-
- foreach my $pkg_class ( @pkg_class ) {
- foreach my $component ( @components ) {
-
- push @items, 'cust_bill_pkg';
-
- push @labels,
- ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
- ( $classnum eq '0'
- ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class )
- : ''
- ).
- ' '.$charge_labels{$component};
-
- my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
- my $row_agentnum = $all_agent || $agent->agentnum;
- push @params, [ ($all_class ? () : ('classnum' => $row_classnum) ),
- ($all_agent ? () : ('agentnum' => $row_agentnum) ),
- 'use_override' => $use_override,
- 'charges' => $component,
- 'average_per_cust_pkg' => $average_per_cust_pkg,
- 'distribute' => $distribute,
- ];
-
- push @links, "$link;".($all_agent ? '' : "agentnum=$row_agentnum;").
- ($all_class ? '' : "classnum=$row_classnum;").
- "distribute=$distribute;".
- "use_override=$use_override;charges=$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;
+ foreach my $part_referral (
+ $all_part_referral ||
+ $sel_part_referral ||
+ qsearch('part_referral', { 'disabled' => '' } )
+ ) {
+
+ foreach my $pkg_class ( @pkg_class ) {
+ foreach my $component ( @components ) {
+
+ push @items, 'cust_bill_pkg';
+
+ push @labels,
+ ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ).
+ ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ).
+ ( $classnum eq '0'
+ ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class )
+ : ''
+ ).
+ ' '.$charge_labels{$component};
+
+ my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
+ my $row_agentnum = $all_agent || $agent->agentnum;
+ my $row_refnum = $all_part_referral || $part_referral->refnum;
+ push @params, [ ($all_class ? () : ('classnum' => $row_classnum) ),
+ ($all_agent ? () : ('agentnum' => $row_agentnum) ),
+ ($all_part_referral ? () : ('refnum' => $row_refnum) ),
+ 'use_override' => $use_override,
+ 'charges' => $component,
+ 'average_per_cust_pkg' => $average_per_cust_pkg,
+ 'distribute' => $distribute,
+ ];
+
+ push @links, "$link;".
+ ($all_agent ? '' : "agentnum=$row_agentnum;").
+ ($all_part_referral ? '' : "refnum=$row_refnum;").
+ ($all_class ? '' : "classnum=$row_classnum;").
+ "distribute=$distribute;".
+ "use_override=$use_override;charges=$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;
+ push @no_graph, 0;
+
+ } #foreach $component
+ } #foreach $pkg_class
+ } #foreach $part_referral
+
+ if ( $cgi->param('agent_totals') and !$all_agent ) {
+ my $row_agentnum = $agent->agentnum;
+ # Include all components that are anywhere on this report
+ my $component = join('', @components);
+
+ my @row_params = ( 'agentnum' => $row_agentnum,
+ 'use_override' => $use_override,
+ 'average_per_cust_pkg' => $average_per_cust_pkg,
+ 'distribute' => $distribute,
+ 'charges' => $component,
+ );
+ my $row_link = "$link;".
+ "agentnum=$row_agentnum;".
+ "distribute=$distribute;".
+ "charges=$component";
+
+ # Also apply any refnum/classnum filters
+ if ( !$all_class and scalar(@pkg_class) == 1 ) {
+ # then a specific class has been chosen, but it may be the empty class
+ my $row_classnum = ref($pkg_class[0]) ? $pkg_class[0]->classnum : 0;
+ push @row_params, 'classnum' => $row_classnum;
+ $row_link .= ";classnum=$row_classnum";
}
+ if ( $sel_part_referral ) {
+ push @row_params, 'refnum' => $sel_part_referral->refnum;
+ $row_link .= ";refnum=".$sel_part_referral->refnum;
+ }
+
+ push @items, 'cust_bill_pkg';
+ push @labels, mt('[_1] - Subtotal', $agent->agent);
+ push @params, \@row_params;
+ push @links, $row_link;
+ push @colors, '000000'; # better idea?
+ push @no_graph, 1;
}
$hue += $hue_increment;
diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html
index 86530dd39..c736de696 100644
--- a/httemplate/graph/elements/monthly.html
+++ b/httemplate/graph/elements/monthly.html
@@ -20,6 +20,7 @@ Example:
'link_fromparam' => 'param_from', #defaults to 'begin'
'link_toparam' => 'param_to', #defaults to 'end'
'daily' => 1, # omit for monthly granularity
+ 'no_graph' => \@no_graph, # items to leave off the graph (subtotals)
#optional, pulled from CGI params if not specified
'start_month' => $smonth,
@@ -35,6 +36,7 @@ Example:
#optional
'agentnum' => $agentnum,
+ 'refnum' => $refnum,
'nototal' => 1,
'graph_type' => 'LinesPoints',
'remove_empty' => 1,
@@ -48,18 +50,19 @@ Example:
'items' => $data->{'items'},
'data' => $data->{'data'},
'row_labels' => $data->{'item_labels'},
- 'graph_labels' => $opt{'graph_labels'} || $data->{'item_labels'},
+ 'graph_labels' => \@graph_labels,
'col_labels' => $col_labels,
'axis_labels' => $data->{label},
- 'colors' => $data->{colors},
+ 'colors' => \@colors,
'links' => \@links,
+ 'no_graph' => \@no_graph,
'bottom_link' => \@bottom_link,
'transpose' => $opt{'daily'},
- map { $_, $opt{$_} } (qw(title
- nototal
- graph_type
- bottom_total
- sprintf
+ map { $_, $opt{$_} } (qw(title
+ nototal
+ graph_type
+ bottom_total
+ sprintf
disable_money
chart_options)),
) %>
@@ -102,7 +105,7 @@ if ( $opt{'daily'} ) { # daily granularity
my %reportopts = (
'items' => \@items,
'params' => $opt{'params'},
- 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/
+ 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/
? $opt{'graph_labels'}
: $opt{'labels'}
),
@@ -117,6 +120,7 @@ my %reportopts = (
'end_year' => $opt{'end_year'},
'projection' => $opt{'projection'},
'agentnum' => $opt{'agentnum'},
+ 'refnum' => $opt{'refnum'},
'remove_empty' => $opt{'remove_empty'},
'doublemonths' => $opt{'doublemonths'},
);
@@ -138,12 +142,20 @@ my $col_labels = [ map { my $m = $_; $m =~ s/^(\d+)\//$mon[$1-1] / ; $m }
@{$data->{label}} ];
$col_labels = $data->{label} if $opt{'daily'};
+my @colors;
+my @graph_labels;
+my @no_graph;
if ( $opt{'remove_empty'} ) {
- # need to filter out series labels for collapsed rows
- $opt{'graph_labels'} = [
- map { $opt{'graph_labels'}[$_] }
- @{ $data->{indices} }
- ];
+ # then filter out per-item things for collapsed rows
+ foreach my $i (@{ $data->{'indices'} }) {
+ push @colors, $opt{'colors'}[$i];
+ push @graph_labels, $opt{'graph_labels'}[$i];
+ push @no_graph, $opt{'no_graph'}[$i];
+ }
+} else {
+ @colors = @{ $opt{'colors'} };
+ @graph_labels = @{ $opt{'graph_labels'} };
+ @no_graph = @{ $opt{'no_graph'} || [] };
}
my @links;
diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html
index 3600f2c66..98b477826 100644
--- a/httemplate/graph/elements/report.html
+++ b/httemplate/graph/elements/report.html
@@ -14,6 +14,7 @@ Example:
'graph_labels' => \@graph_labels, #defaults to row_labels
'links' => \@links, #optional
+ 'no_graph' => \@no_graph, #optional
#these run parallel to the elements of each @item
'col_labels' => \@col_labels, #required
@@ -77,15 +78,16 @@ any delimiter and linked from the elements in @data.
% }
%
% } elsif ( $cgi->param('_type') =~ /(xls)$/ ) {
-%
-% #http_header('Content-Type' => 'application/excel' ); #eww
-% http_header('Content-Type' => 'application/vnd.ms-excel' );
-% #http_header('Content-Type' => 'application/msexcel' ); #alas
-% http_header('Content-Disposition' => "attachment;filename=$filename.xls");
+% #false laziness w/ search/elements/search-xls
+% my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format;
+% $filename .= $format->{extension};
+%
+% http_header('Content-Type' => $format->{mime_type} );
+% http_header('Content-Disposition' => qq!attachment;filename="$filename"! );
%
% my $output = '';
% my $XLS = new IO::Scalar \$output;
-% my $workbook = Spreadsheet::WriteExcel->new($XLS)
+% my $workbook = $format->{class}->new($XLS)
% or die "Error opening .xls file: $!";
%
% my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
@@ -127,7 +129,19 @@ any delimiter and linked from the elements in @data.
%
<% $output %>
% } elsif ( $cgi->param('_type') eq 'png' ) {
-%
+% # delete any items that shouldn't be on the graph
+% if ( my $no_graph = $opt{'no_graph'} ) {
+% my $i = 0;
+% while (@$no_graph) {
+% if ( shift @$no_graph ) {
+% splice @data, $i, 1;
+% splice @{$opt{'graph_labels'}}, $i, 1;
+% splice @{$opt{'colors'}}, $i, 1;
+% $i--; # because everything is shifted down
+% }
+% $i++;
+% }
+% }
% my $graph_type = 'LinesPoints';
% if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain|Bars)$/ ) {
% $graph_type = $1;
@@ -304,9 +318,6 @@ td.cell {
<% include('/elements/footer.html') %>
% }
-<%once>
-
-</%once>
<%init>
my(%opt) = @_;
diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi
index cde71be76..166735fc6 100644
--- a/httemplate/graph/money_time.cgi
+++ b/httemplate/graph/money_time.cgi
@@ -1,5 +1,5 @@
<% include('elements/monthly.html',
- 'title' => $agentname.
+ 'title' => $agentname. $referralname.
'Sales, Credits and Receipts Summary',
'items' => \@items,
'labels' => \%label,
@@ -7,6 +7,7 @@
'colors' => \%color,
'links' => \%link,
'agentnum' => $agentnum,
+ 'refnum' => $refnum,
'nototal' => scalar($cgi->param('12mo')),
)
%>
@@ -22,9 +23,17 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$agent = qsearchs('agent', { 'agentnum' => $agentnum } );
die "agentnum $agentnum not found!" unless $agent;
}
-
my $agentname = $agent ? $agent->agent.' ' : '';
+my( $refnum, $part_referral ) = ('', '');
+if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ $refnum = $1;
+ $part_referral = qsearchs('part_referral', { 'refnum' => $refnum } );
+ die "refnum $refnum not found!" unless $part_referral;
+}
+my $referralname = $part_referral ? $part_referral->referral.' ' : '';
+
+
my @items = qw( invoiced netsales
credits netcredits
payments receipts
@@ -83,15 +92,17 @@ my %color = (
$color{$_.'_12mo'} = $color{$_}
foreach keys %color;
+my $ar = "agentnum=$agentnum;refnum=$refnum";
+
my %link = (
- 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;",
- 'netsales' => "${p}search/cust_bill.html?agentnum=$agentnum;net=1;",
- 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;",
- 'netcredits' => "${p}search/cust_credit_bill.html?agentnum=$agentnum;",
- 'payments' => "${p}search/cust_pay.html?magic=_date;agentnum=$agentnum;",
- 'receipts' => "${p}search/cust_bill_pay.html?agentnum=$agentnum;",
- 'refunds' => "${p}search/cust_refund.html?magic=_date;agentnum=$agentnum;",
- 'netrefunds' => "${p}search/cust_credit_refund.html?agentnum=$agentnum;",
+ 'invoiced' => "${p}search/cust_bill.html?$ar;",
+ 'netsales' => "${p}search/cust_bill.html?$ar;net=1;",
+ 'credits' => "${p}search/cust_credit.html?$ar;",
+ 'netcredits' => "${p}search/cust_credit_bill.html?$ar;",
+ 'payments' => "${p}search/cust_pay.html?magic=_date;$ar;",
+ 'receipts' => "${p}search/cust_bill_pay.html?$ar;",
+ 'refunds' => "${p}search/cust_refund.html?magic=_date;$ar;",
+ 'netrefunds' => "${p}search/cust_credit_refund.html?$ar;",
);
# XXX link 12mo?
diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html
index 07d4421e8..31792e8dd 100644
--- a/httemplate/graph/report_cust_bill_pkg.html
+++ b/httemplate/graph/report_cust_bill_pkg.html
@@ -11,20 +11,45 @@
<TD>Show projected data for future months</TD>
</TR>
-<% include('/elements/tr-select-agent.html',
- 'label' => 'For agent: ',
- 'disable_empty' => 0,
- 'pre_options' => [ 'all' => 'all (aggregate)' ],
- 'empty_label' => 'all (breakdown)',
- )
-%>
-
-<% include('/elements/tr-select-pkg_class.html',
- 'pre_options' => [ 'all' => 'all (aggregate)',
- '0' => 'all (breakdown)' ],
- 'empty_label' => '(empty class)',
- )
-%>
+<SCRIPT TYPE="text/javascript">
+function enable_agent_totals(obj) {
+%# enable it iff we are breaking down by agent AND something else
+ obj.form.agent_totals.disabled = !(
+ obj.form.agentnum.value == '' && (
+ obj.form.refnum.value == '' ||
+ obj.form.classnum.value == 0 ||
+ obj.form.use_setup.value == 1 ||
+ obj.form.use_usage.value == 1
+ )
+ );
+}
+</SCRIPT>
+
+<& /elements/tr-select-agent.html,
+ 'field' => 'agentnum',
+ 'label' => 'Agent ',
+ 'disable_empty' => 0,
+ 'pre_options' => [ 'all' => 'all (aggregate)' ],
+ 'empty_label' => 'all (breakdown)',
+ 'onchange' => 'enable_agent_totals',
+&>
+
+<& /elements/tr-select-part_referral.html,
+ 'field' => 'refnum',
+ 'label' => 'Advertising source ',
+ 'disable_empty' => 0,
+ 'pre_options' => [ 'all' => 'all (aggregate)' ],
+ 'empty_label' => 'all (breakdown)',
+ 'onchange' => 'enable_agent_totals'
+&>
+
+<& /elements/tr-select-pkg_class.html,
+ 'field' => 'classnum',
+ 'pre_options' => [ 'all' => 'all (aggregate)',
+ '0' => 'all (breakdown)' ],
+ 'empty_label' => '(empty class)',
+ 'onchange' => 'enable_agent_totals',
+&>
<!--
<TR>
@@ -39,10 +64,16 @@
'field' => 'use_'.lc($_),
'options' => [ 0, 1, 2 ],
'labels' => { 0 => 'Combine', 1 => 'Separate', 2 => 'Do not show' },
+ 'onchange'=> 'enable_agent_totals',
&>
% }
<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="agent_totals" VALUE="1" DISABLED="1"></TD>
+ <TD>Show per-agent subtotals</TD>
+</TR>
+
+<TR>
<TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD>
<TD>Separate sub-packages from parents</TD>
</TR>
diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html
index b85bb6552..97876c996 100644
--- a/httemplate/graph/report_money_time.html
+++ b/httemplate/graph/report_money_time.html
@@ -24,6 +24,13 @@
)
%>
+<% include('/elements/tr-select-part_referral.html',
+ 'label' => 'Advertising source ',
+ 'disable_empty' => 0,
+ 'empty_label' => 'all',
+ )
+%>
+
<TR>
<TD ALIGN="right"><INPUT TYPE="checkbox" NAME="12mo" VALUE="1"></TD>
<TD>Show 12 month totals instead of monthly values</TD>
diff --git a/httemplate/index.html b/httemplate/index.html
index ae1509610..71926aa4e 100644
--- a/httemplate/index.html
+++ b/httemplate/index.html
@@ -1,9 +1,18 @@
+<%init>my $debug = $cgi->param('debug');</%init>
+% warn time.": header.html\n" if $debug;
+%
<& /elements/header.html, mt('Billing Main') &>
+% warn time.": dashboard-install_welcome.html\n" if $debug;
+%
<& /elements/dashboard-install_welcome.html &>
+% warn time.": dashboard-toplist.html\n" if $debug;
+%
<& /elements/dashboard-toplist.html &>
+% warn time.": fetching recently changed customers\n" if $debug;
+%
% my $sth = dbh->prepare(
% #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum )
% "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum )
@@ -20,6 +29,7 @@
% @custnums = splice(@custnums, 0, 10);
%
% if ( @custnums ) {
+% warn time.": displaying recently changed customers\n" if $debug;
<& /elements/table-grid.html &>
diff --git a/httemplate/loginout/logout.html b/httemplate/loginout/logout.html
index 40371d596..d8e1c634a 100644
--- a/httemplate/loginout/logout.html
+++ b/httemplate/loginout/logout.html
@@ -8,11 +8,11 @@
<BODY>
<BR><BR>
<CENTER>
- You have logged out
+ You have logged out.
</CENTER>
<BR><BR>
<CENTER>
- Return to <a href="..">freeside</a>
+ You can <a href="..">log in</a> again.
</CENTER>
</BODY>
</HTML>
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 2e798652d..887b92489 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -1,6 +1,9 @@
-<% include('/elements/header.html', 'Quick payment entry') %>
+<& /elements/header.html, {
+ title => 'Quick payment entry',
+ etc => 'onload="preload()"'
+} &>
-<% include('/elements/error.html') %>
+<& /elements/error.html &>
<SCRIPT TYPE="text/javascript">
function warnUnload() {
@@ -14,6 +17,21 @@ function warnUnload() {
}
window.onbeforeunload = warnUnload;
+function add_row_callback(rownum, prefix) {
+ document.getElementById('enable_app'+rownum).disabled = true;
+}
+
+function custnum_update_callback(rownum, prefix) {
+ var custnum = document.getElementById('custnum'+rownum).value;
+ document.getElementById('enable_app'+rownum).disabled = (
+ custnum == 0 ||
+ num_open_invoices[rownum] < 2
+ );
+% if ( $use_discounts ) {
+ select_discount_term(rownum, prefix);
+% }
+}
+
function select_discount_term(row, prefix) {
var custnum_obj = document.getElementById('custnum'+prefix+row);
var select_obj = document.getElementById('discount_term'+prefix+row);
@@ -46,6 +64,265 @@ function select_discount_term(row, prefix) {
discount_terms(custnum_obj.value, select_discount_term_update);
}
+
+var invoices_for_row = new Object;
+
+function update_invoices(rownum, invoices) {
+ invoices_for_row[rownum] = new Object;
+ // only called before create_application_row
+ for ( var i=0; i<invoices.length; i++ ) {
+ invoices_for_row[rownum][ invoices[i].invnum ] = invoices[i];
+ }
+}
+
+function toggle_application_row(ev, next) {
+ if (!next) next = function(){}; //optional continuation
+ var rownum = this.getAttribute('rownum');
+ if ( this.checked ) {
+ var custnum = document.getElementById('custnum'+rownum).value;
+ if (!custnum) return;
+ lock_payment_row(rownum, true);
+ custnum_search_open( custnum,
+ function(returned) {
+ update_invoices(rownum, JSON.parse(returned));
+ create_application_row(rownum, 0);
+ next.call(this, rownum);
+ }
+ );
+ }
+}
+
+function lock_payment_row(rownum, flag) {
+% foreach (qw(invnum custnum customer)) {
+ obj = document.getElementById('<% $_ %>'+rownum);
+ obj.readOnly = flag;
+% }
+ document.getElementById('enable_app'+rownum).disabled = flag;
+}
+
+function delete_application_row() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+ var tr_app = document.getElementById('row'+rownum+'.'+appnum);
+ var select_invnum = document.getElementById('invnum'+rownum+'.'+appnum);
+ if ( select_invnum.value ) {
+ invoices_for_row[rownum][ select_invnum.value ] = select_invnum.curr_invoice;
+ }
+
+ tr_app.parentNode.removeChild(tr_app);
+ if ( appnum > 0 ) {
+ document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = '';
+ }
+ else {
+ lock_payment_row(rownum, false);
+ document.getElementById('enable_app'+rownum).checked = false;
+ }
+}
+
+function amount_unapplied(rownum) {
+ var appnum = 0;
+ var total = 0;
+ var payment_amount = parseFloat(document.getElementById('paid'+rownum).value)
+ || 0;
+ while (true) {
+ var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+ if ( input_amount ) {
+ total += parseFloat(input_amount.value || 0);
+ appnum++;
+ }
+ else {
+ return payment_amount - total;
+ }
+ }
+}
+
+var change_app_amount;
+
+function choose_app_invnum() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+ var last_invoice = this.curr_invoice;
+ if ( last_invoice ) {
+ invoices_for_row[rownum][ last_invoice['invnum'] ] = last_invoice;
+ }
+
+ if ( this.value ) {
+ var this_invoice = invoices_for_row[rownum][this.value];
+ this.curr_invoice = invoices_for_row[rownum][this.value];
+ var span_owed = document.getElementById('owed'+rownum+'.'+appnum);
+ span_owed.innerHTML = this_invoice['owed'] + '&nbsp;';
+ delete invoices_for_row[rownum][this.value];
+
+ var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+ if ( input_amount.value == '' ) {
+ input_amount.value =
+ Math.max(
+ 0, Math.min( amount_unapplied(rownum), this_invoice['owed'])
+ ).toFixed(2);
+ // trigger onchange
+ change_app_amount.call(input_amount);
+ }
+ }
+}
+
+function focus_app_invnum() {
+% # invoice numbers just display as invoice numbers
+ var rownum = this.getAttribute('rownum');
+ var add_opt = function(obj, value) {
+ var o = document.createElement('OPTION');
+ o.text = value;
+ o.value = value;
+ obj.add(o);
+ }
+ this.options.length = 0;
+ var this_invoice = this.curr_invoice;
+ if ( this_invoice ) {
+ add_opt(this, this_invoice.invnum);
+ } else {
+ add_opt(this, '');
+ }
+ for ( var x in invoices_for_row[rownum] ) {
+ add_opt(this, invoices_for_row[rownum][x].invnum);
+ }
+}
+
+function change_app_amount() {
+ var rownum = this.getAttribute('rownum');
+ var appnum = this.getAttribute('appnum');
+%# maybe some kind of warning if amount_unapplied < 0?
+%# only spawn a new application row if there are open invoices left,
+%# and this is the highest-numbered application row for the customer,
+%# and the sum of the applied amounts is < the amount of the payment,
+ if ( Object.keys(invoices_for_row[rownum]).length > 0
+ && !document.getElementById( 'row'+rownum+'.'+(parseInt(appnum) + 1) )
+ && amount_unapplied(rownum) > 0 ) {
+
+ create_application_row(rownum, parseInt(appnum) + 1);
+
+ }
+}
+
+function create_application_row(rownum, appnum) {
+ var payment_row = document.getElementById('row'+rownum);
+ var tr_app = document.createElement('TR');
+ tr_app.setAttribute('rownum', rownum);
+ tr_app.setAttribute('appnum', appnum);
+ tr_app.setAttribute('id', 'row'+rownum+'.'+appnum);
+
+ var td_invnum = document.createElement('TD');
+ td_invnum.setAttribute('colspan', 4);
+ td_invnum.style.textAlign = 'right';
+ td_invnum.appendChild(
+ document.createTextNode('<% mt('Apply to Invoice ') %>')
+ );
+ var select_invnum = document.createElement('SELECT');
+ select_invnum.setAttribute('rownum', rownum);
+ select_invnum.setAttribute('appnum', appnum);
+ select_invnum.setAttribute('id', 'invnum'+rownum+'.'+appnum);
+ select_invnum.setAttribute('name', 'invnum'+rownum+'.'+appnum);
+ select_invnum.style.textAlign = 'right';
+ select_invnum.style.width = '50px';
+ select_invnum.onchange = choose_app_invnum;
+ select_invnum.onfocus = focus_app_invnum;
+
+ td_invnum.appendChild(select_invnum);
+ tr_app.appendChild(td_invnum);
+
+ var td_owed = document.createElement('TD');
+ td_owed.style.textAlign= 'right';
+ var span_owed = document.createElement('SPAN');
+ span_owed.setAttribute('rownum', rownum);
+ span_owed.setAttribute('appnum', appnum);
+ span_owed.setAttribute('id', 'owed'+rownum+'.'+appnum);
+ td_owed.appendChild(span_owed);
+ tr_app.appendChild(td_owed);
+
+ var td_amount = document.createElement('TD');
+ td_amount.style.textAlign = 'right';
+ var input_amount = document.createElement('INPUT');
+ input_amount.size = 6;
+ input_amount.setAttribute('rownum', rownum);
+ input_amount.setAttribute('appnum', appnum);
+ input_amount.setAttribute('name', 'amount'+rownum+'.'+appnum);
+ input_amount.setAttribute('id', 'amount'+rownum+'.'+appnum);
+ input_amount.style.textAlign = 'right';
+ input_amount.onchange = change_app_amount;
+ td_amount.appendChild(input_amount);
+ tr_app.appendChild(td_amount);
+
+ var td_delete = document.createElement('TD');
+ td_delete.setAttribute('colspan', <% scalar(@fields)-2 %>);
+ var button_delete = document.createElement('INPUT');
+ button_delete.setAttribute('rownum', rownum);
+ button_delete.setAttribute('appnum', appnum);
+ button_delete.setAttribute('id', 'delete'+rownum+'.'+appnum);
+ button_delete.setAttribute('type', 'button');
+ button_delete.setAttribute('value', 'X');
+ button_delete.onclick = delete_application_row;
+ button_delete.style.color = '#ff0000';
+ button_delete.style.fontWeight = 'bold';
+ button_delete.style.paddingLeft = '2px';
+ button_delete.style.paddingRight = '2px';
+ td_delete.appendChild(button_delete);
+ tr_app.appendChild(td_delete);
+
+ var td_error = document.createElement('TD');
+ var span_error = document.createElement('SPAN');
+ span_error.setAttribute('rownum', rownum);
+ span_error.setAttribute('appnum', appnum);
+ span_error.setAttribute('id', 'error'+rownum+'.'+appnum);
+ span_error.style.color = '#ff0000';
+ td_error.appendChild(span_error);
+ tr_app.appendChild(td_error);
+
+ if ( appnum > 0 ) {
+ //remove delete button on the previous row
+ document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = 'none';
+ }
+ rownum++;
+ var next_row = document.getElementById('row'+rownum); // always exists
+ payment_row.parentNode.insertBefore(tr_app, next_row);
+
+}
+
+%# for error handling--ugly, but the alternative is translating the whole
+%# process of creating rows into Mason
+var row_array = <% encode_json(\@rows) %>;
+function preload() {
+ var rownum;
+ var appnum;
+ for (rownum=0; rownum < row_array.length; rownum++) {
+ if ( row_array[rownum].length ) {
+ var enable = document.getElementById('enable_app'+rownum);
+ enable.checked = true;
+ var preload_row = function(r) {//continuation from toggle_application_row
+ for (appnum=0; appnum < row_array[r].length; appnum++) {
+ this_app = row_array[r][appnum];
+ var x = r + '.' + appnum;
+ //set invnum
+ var select_invnum = document.getElementById('invnum'+x);
+ focus_app_invnum.call(select_invnum);
+ for (i=0; i<select_invnum.options.length; i++) {
+ if (select_invnum.options[i].value == this_app.invnum) {
+ select_invnum.selectedIndex = i;
+ }
+ }
+ choose_app_invnum.call(select_invnum);
+ //set amount
+ var input_amount = document.getElementById('amount'+x);
+ input_amount.value = this_app.amount;
+
+ //set error
+ var span_error = document.getElementById('error'+x);
+ span_error.innerHTML = this_app.error;
+ change_app_amount.call(input_amount); //creates next row
+ } //for appnum
+ }; //preload_row function
+ toggle_application_row.call(enable, null, preload_row);
+ } // if row_array[rownum].length
+ } //for rownum
+}
+
</SCRIPT>
<% include('/elements/xmlhttp.html',
@@ -57,21 +334,26 @@ function select_discount_term(row, prefix) {
<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.btnsubmit.disabled=true;window.onbeforeunload = null;">
<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
+<& /elements/xmlhttp.html,
+ url => $p.'misc/xmlhttp-cust_bill-search.html',
+ subs => ['custnum_search_open']
+&>
-<% include( "/elements/customer-table.html",
- name_singular => 'payment',
- header => \@header,
- fields => \@fields,
- type => \@types,
- align => \@align,
- size => \@sizes,
- color => \@colors,
- param => \%param,
- footer => \@footer,
- footer_align => \@footer_align,
- custnum_update_callback => $custnum_update_callback,
- )
-%>
+<& /elements/customer-table.html,
+ name_singular => 'payment',
+ header => \@header,
+ fields => \@fields,
+ type => \@types,
+ align => \@align,
+ size => \@sizes,
+ color => \@colors,
+ param => \%param,
+ footer => \@footer,
+ footer_align => \@footer_align,
+ onchange => \@onchange,
+ custnum_update_callback => 'custnum_update_callback',
+ add_row_callback => 'add_row_callback',
+&>
<BR>
<INPUT TYPE="button" VALUE="Post payment batch" name="btnsubmit" onclick="window.onbeforeunload = null; document.OneTrueForm.submit(); this.disabled = true;">
@@ -105,7 +387,8 @@ my @colors = ( '', '' );
my %param = ();
my @footer = ( '_TOTAL', '' );
my @footer_align = ( 'r', 'r' );
-my $custnum_update_callback = '';
+my @onchange = ( '', '' );;
+my $use_discounts = '';
if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
#push @header, 'Discount';
@@ -117,9 +400,20 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
push @colors, '';
push @footer, '';
push @footer_align, '';
- $custnum_update_callback = 'select_discount_term';
+ push @onchange, '';
+ $use_discounts = 'Y';
}
+push @header, 'Allocate';
+push @fields, 'enable_app';
+push @types, 'checkbox';
+push @align, 'c';
+push @sizes, '0';
+push @colors, '';
+push @footer, '';
+push @footer_align, '';
+push @onchange, 'toggle_application_row';
+
#push @header, 'Error';
push @header, '';
push @fields, 'error';
@@ -129,7 +423,34 @@ push @sizes, '0';
push @colors, '#ff0000';
push @footer, '';
push @footer_align, '';
+push @onchange, '';
$m->comp('/elements/handle_uri_query');
+# set up for preloading
+my @rows;
+my @row_errors;
+if ( $cgi->param('error') ) {
+ my $param = $cgi->Vars;
+ my $enum = 0; #errors numbered separately
+ for( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+ $rows[$row] = [];
+ $row_errors[$row] = $param->{"error$enum"};
+ $enum++;
+ for( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+ next if !$param->{"invnum$row.$app"};
+ my %this_app = map { $_ => ($param->{$_.$row.'.'.$app} || '') }
+ qw( invnum amount );
+ $this_app{'error'} = $param->{"error$enum"} || '';
+ $param->{"error$enum"} = ''; # don't pass this error through
+ $rows[$row][$app] = \%this_app;
+ $enum++;
+ }
+ }
+ for( my $row = 0; $row < @row_errors; $row++ ) {
+ $param->{"error$row"} = $row_errors[$row];
+ }
+}
+#warn Dumper {rows => \@rows, row_errors => \@row_errors };
+
</%init>
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
index 4b5df8654..f9a46a898 100755
--- a/httemplate/misc/cancel_pkg.html
+++ b/httemplate/misc/cancel_pkg.html
@@ -13,50 +13,94 @@
% my $date_init = 0;
% if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
% $submit =~ /^(\w*)\s/;
-<& /elements/tr-input-date-field.html, {
- 'name' => 'date',
- 'value' => $date,
- 'label' => mt("$1 package on"),
- 'format' => $date_format,
-} &>
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'date',
+ 'value' => $date,
+ 'label' => mt("$1 package on"),
+ 'format' => $date_format,
+ } &>
% $date_init = 1;
% }
-% unless ( $method eq 'resume' ) { #the only one that doesn't need a reason
-<& /elements/tr-select-reason.html,
- 'field' => 'reasonnum',
- 'reason_class' => $class,
- 'curr_value' => $reasonnum,
- 'control_button' => "document.getElementById('confirm_cancel_pkg_button')",
-&>
+% if ($method eq 'uncancel' ) {
+%
+% #XXX customer also requested setup
+% # setup: what usefulness is changing or blanking this? re-charge setup fee?
+% # an option that says that would be better if that's what we want to do
+
+% # last_bill: isn't this informational? what good would editing it do?
+% # something about invoice display?
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'last_bill',
+ 'value' => ( $cgi->param('last_bill') || $cust_pkg->get('last_bill') ),
+ 'label' => mt("Last bill date"),
+ 'format' => $date_format,
+ } &>
+
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'bill',
+ 'value' => ( $cgi->param('bill') || $cust_pkg->get('bill') ),
+ 'label' => mt("Next bill date"),
+ 'format' => $date_format,
+ } &>
+
+ <& /elements/tr-checkbox.html,
+ 'label' => mt("Uncancel even if a service can't be re-provisioned"),
+ 'field' => 'svc_not_fatal',
+ 'value' => 'Y',
+ &>
+
+% $date_init = 1;
+% }
+
+% unless ( $method eq 'resume' || $method eq 'uncancel' ) {
+ <& /elements/tr-select-reason.html,
+ field => 'reasonnum',
+ reason_class => $class,
+ curr_value => $reasonnum,
+ control_button => "document.getElementById('confirm_cancel_pkg_button')",
+ &>
% }
-% if ( ( $method eq 'adjourn' or $method eq 'suspend' ) and
+% if ( $method eq 'adjourn' || $method eq 'suspend' ) {
+ <TR><TD COLSPAN=2>
+% if ( $part_pkg->option('suspend_bill', 1) ) {
+ <& /elements/checkbox.html, name=>'no_suspend_bill', value=>'Y' &>
+ Disable recurring billing while suspended
+% } else {
+ <& /elements/checkbox.html, name=>'suspend_bill', value=>'Y' &>
+ Continue recurring billing while suspended
+% }
+ </TD></TR>
+% }
+
+% if ( ( $method eq 'adjourn' || $method eq 'suspend' ) and
% $curuser->access_right('Unsuspend customer package') ) { #later?
% my $resume_date = $cgi->param('error')
% ? str2time($cgi->param('resume_date'))
% : $cust_pkg->get('resume');
-<& /elements/tr-input-date-field.html, {
- 'name' => 'resume_date',
- 'value' => $resume_date,
- 'label' => mt('Unsuspend on'),
- 'format' => $date_format,
- 'noinit' => $date_init,
-} &>
+ <& /elements/tr-input-date-field.html, {
+ 'name' => 'resume_date',
+ 'value' => $resume_date,
+ 'label' => mt('Unsuspend on'),
+ 'format' => $date_format,
+ 'noinit' => $date_init,
+ } &>
% }
</TABLE>
<BR>
<INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_pkg_button"
VALUE="<% mt($submit) |h %>"
- <% $method ne 'resume' ? 'DISABLED' : '' %>>
+ <% $method !~ /^(resume|uncancel)$/ ? 'DISABLED' : '' %>>
</FORM>
</BODY>
</HTML>
<%init>
+use Date::Parse qw(str2time);
my $conf = new FS::Conf;
my $date_format = $conf->config('date_format') || '%m/%d/%Y';
@@ -99,6 +143,10 @@ if ($method eq 'cancel') {
$class = '';
$submit = 'Unsuspend Later';
$right = 'Unsuspend customer package'; #later?
+} elsif ( $method eq 'uncancel') {
+ $class = '';
+ $submit = 'Un-Cancel';
+ $right = 'Un-cancel customer package'; #later?
} else {
die 'illegal query (unknown method param)';
}
@@ -107,6 +155,7 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied" unless $curuser->access_right($right);
my $title = ucfirst($method) . ' Package';
+$title =~ s/Uncancel/Un-cancel/;
my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
or die "Unknown pkgnum: $pkgnum";
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
index dcd033ff2..a277ba407 100644
--- a/httemplate/misc/cust-part_pkg.cgi
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -1,11 +1,19 @@
<% objToJson( \@return ) %>
<%init>
-my( $custnum, $classnum ) = $cgi->param('arg');
+my( $custnum, $prospectnum, $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 $agent;
+if ( $custnum ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die 'unknown custnum';
+ $agent = $cust_main->agent;
+} else {
+ my $prospect_main = qsearchs('prospect_main', {'prospectnum'=>$prospectnum} )
+ or die 'unknown prospectnum';
+ $agent = $prospect_main->agent;
+}
my %hash = ( 'disabled' => '' );
if ( $classnum > 0 ) {
@@ -19,7 +27,7 @@ my @part_pkg = qsearch({
'hashref' => \%hash,
'extra_sql' =>
' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ).
- ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ),
+ ' AND '. FS::part_pkg->agent_pkgs_sql( $agent ),
'order_by' => 'ORDER BY pkg',
});
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
index 002c0011a..2ae9f1021 100755
--- a/httemplate/misc/cust_main-cancel.cgi
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -1,4 +1,4 @@
-<& /elements/header.html, mt("Customer cancelled") &>
+<& /elements/header-popup.html, mt("Customer cancelled") &>
<SCRIPT TYPE="text/javascript">
window.top.location.reload();
</SCRIPT>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
index 664651069..d56feaccf 100644
--- a/httemplate/misc/cust_main-import.cgi
+++ b/httemplate/misc/cust_main-import.cgi
@@ -35,6 +35,8 @@ Import a file containing customer records.
<OPTION VALUE="extended-plus_company_and_options">Extended plus company and options
<OPTION VALUE="svc_external">External service
<OPTION VALUE="svc_external_svc_phone">External service and phone service
+ <OPTION VALUE="birthdates-acct_phone_hardware">Birthdates and account, phone and hardware services
+ <OPTION VALUE="national_id-acct_phone">National ID, plus account and phone services
</SELECT>
</TD>
</TR>
@@ -106,6 +108,12 @@ Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets.
<b>External service and phone service</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, id, title, countrycode, phonenum, sip_password, pin</i>
<BR><BR>
+<b>Birthdates and account, phone and hardware services</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, birthdate, spouse_birthdate, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, username, _password, countrycode, phonenum, sip_password, pin, typenum, ip_addr, hw_addr, serial</i>
+<BR><BR>
+
+<b>National ID, plus account and phone services</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, national_id, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, username, _password, slipip, countrycode, phonenum, sip_password, pin</i>
+<BR><BR>
+
<%$req%> Required fields
<BR><BR>
@@ -131,6 +139,8 @@ advertising source table.
<li><i>username</i> and <i>_password</i> are required if <i>pkgpart</i> is specified. (Extended and Extended plus company formats)
+ <li><i>slipip</i>: IP address
+
<li><i>id</i>: External service id, integer
<li><i>title</i>: External service identifier, text
diff --git a/httemplate/misc/cust_main-suspend.cgi b/httemplate/misc/cust_main-suspend.cgi
new file mode 100755
index 000000000..61851364e
--- /dev/null
+++ b/httemplate/misc/cust_main-suspend.cgi
@@ -0,0 +1,75 @@
+<& /elements/header-popup.html, mt("Customer suspended") &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+#false laziness w/cust_main-cancel.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Suspend customer');
+
+my $custnum;
+my $adjourn = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $adjourn = $cgi->param('adjourn');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ || die "Illegal custnum";
+ $custnum = $1;
+}
+
+#false laziness w/process/cancel_pkg.html
+
+#untaint reasonnum
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
+$reasonnum = $1;
+
+if ($reasonnum == -1) {
+ $reasonnum = {
+ 'typenum' => scalar( $cgi->param('newreasonnumT') ),
+ 'reason' => scalar( $cgi->param('newreasonnum' ) ),
+ };
+}
+
+#eslaf
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+
+my @errors;
+if($cgi->param('now_or_later')) {
+ $adjourn = parse_datetime($adjourn);
+ if($adjourn) {
+ #warn "setting adjourn dates on custnum#$custnum\n";
+ my @pkgs = $cust_main->unsuspended_pkgs;
+ @errors = grep {$_} map { $_->suspend(
+ 'reason' => $reasonnum,
+ 'date' => $adjourn,
+ ) } @pkgs;
+ }
+ else {
+ @errors = ("error parsing adjourn date: ".$cgi->param('adjourn'));
+ }
+}
+else {
+ warn "suspending $cust_main";
+ @errors = $cust_main->suspend(
+ 'reason' => $reasonnum,
+ );
+}
+my $error = join(' / ', @errors) if scalar(@errors);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(1). "suspend_cust.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/cust_main-unsuspend.cgi b/httemplate/misc/cust_main-unsuspend.cgi
new file mode 100755
index 000000000..eb4a2c8f8
--- /dev/null
+++ b/httemplate/misc/cust_main-unsuspend.cgi
@@ -0,0 +1,56 @@
+<& /elements/header-popup.html, mt("Customer unsuspended") &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+<%init>
+
+#false laziness w/cust_main-cancel.cgi
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer');
+
+my $custnum;
+my $resume = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $resume = $cgi->param('resume');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ || die "Illegal custnum";
+ $custnum = $1;
+}
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+
+my @errors;
+if($cgi->param('now_or_later')) {
+ $resume = parse_datetime($resume);
+ if($resume) {
+ #warn "setting resume dates on custnum#$custnum\n";
+ my @pkgs = $cust_main->suspended_pkgs;
+ @errors = grep {$_} map { $_->unsuspend(
+ 'date' => $resume,
+ ) } @pkgs;
+ }
+ else {
+ @errors = ("error parsing adjourn date: ".$cgi->param('adjourn'));
+ }
+}
+else {
+ warn "unsuspending $cust_main";
+ @errors = $cust_main->unsuspend;
+}
+my $error = join(' / ', @errors) if scalar(@errors);
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(1). "unsuspend_cust.html?". $cgi->query_string );
+}
+
+</%init>
diff --git a/httemplate/misc/delete-ftp_target.html b/httemplate/misc/delete-ftp_target.html
new file mode 100644
index 000000000..c8bd29701
--- /dev/null
+++ b/httemplate/misc/delete-ftp_target.html
@@ -0,0 +1,18 @@
+% if ( $error ) {
+% errorpage($error);
+% } else {
+<% $cgi->redirect("${p}browse/ftp_target.html") %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal targetnum";
+my $targetnum = $1;
+
+my $target = qsearchs('ftp_target',{'targetnum'=>$targetnum});
+my $error = $target->delete;
+
+</%init>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
index 23deba712..f3a31eb3b 100644
--- a/httemplate/misc/download-batch.cgi
+++ b/httemplate/misc/download-batch.cgi
@@ -1,4 +1,4 @@
-<% $pay_batch->export_batch($format) %><%init>
+<% $pay_batch->export_batch(%opt) %><%init>
#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
@@ -10,9 +10,14 @@ if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
die "No batch number (bad URL) \n";
}
-my $format;
-if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
- $format = $1;
+my %opt;
+if ( $cgi->param('gatewaynum') =~ /^(\d+)$/ ) {
+ my $gateway = FS::payment_gateway->by_key($1);
+ die "gatewaynum $1 not found" unless $gateway;
+ $opt{'gateway'} = $gateway;
+}
+elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
+ $opt{'format'} = $1;
}
my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index 66717b5bf..bfc7b6903 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -1,4 +1,6 @@
-<& /elements/header-popup.html, mt('Order new package') &>
+<& /elements/header-popup.html, $quotationnum ? mt('Add package to quotation')
+ : mt('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>
@@ -11,8 +13,10 @@
<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST">
-<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main ? $cust_main->custnum : '' %>">
+<INPUT TYPE="hidden" NAME="prospectnum" VALUE="<% $prospect_main ? $prospect_main->prospectnum : '' %>">
<INPUT TYPE="hidden" NAME="qualnum" VALUE="<% scalar($cgi->param('qualnum')) |h %>">
+<INPUT TYPE="hidden" NAME="quotationnum" VALUE="<% $quotationnum %>">
% if ( $svcpart ) {
<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
% }
@@ -26,12 +30,24 @@
</TR>
% } else {
<& /elements/tr-select-cust-part_pkg.html,
- 'curr_value' => $pkgpart,
- 'classnum' => -1,
- 'cust_main' => $cust_main,
+ 'curr_value' => $pkgpart,
+ 'classnum' => -1,
+ 'cust_main' => $cust_main,
+ 'prospect_main' => $prospect_main,
&>
% }
+% if ( $conf->exists('invoice-unitprice') ) {
+ <TR>
+ <TH ALIGN="right"><% mt('Quantity') |h %> </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>">
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="quantity" VALUE="1">
+% }
+
<TR>
<TH ALIGN="right"><% mt('Start date') |h %> </TD>
<TD COLSPAN=6>
@@ -45,7 +61,7 @@
</TD>
</TR>
-% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
+% if ( $cust_main && $cust_main->payby =~ /^(CARD|CHEK)$/ ) {
% my $what = lc(FS::payby->shortname($cust_main->payby));
<TR>
<TH ALIGN="right"><% mt("Disable automatic $what charge") |h %> </TH>
@@ -88,8 +104,9 @@
% } else {
<& /elements/tr-select-cust_location.html,
- 'cgi' => $cgi,
- 'cust_main' => $cust_main,
+ 'cgi' => $cgi,
+ 'cust_main' => $cust_main,
+ 'prospect_main' => $prospect_main,
&>
% }
@@ -144,29 +161,56 @@ die "access denied"
my $conf = new FS::Conf;
my $date_format = $conf->config('date_format') || '%m/%d/%Y';
-$cgi->param('custnum') =~ /^(\d+)$/ or die "no custnum";
-my $custnum = $1;
-my $cust_main = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => { 'custnum' => $custnum },
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-});
+my $cust_main = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ my $custnum = $1;
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ });
+}
+
+my $prospect_main = '';
+if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ my $prospectnum = $1;
+ $prospect_main = qsearchs({
+ 'table' => 'prospect_main',
+ 'hashref' => { 'prospectnum' => $prospectnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ });
+}
+
+my $quotationnum = '';
+if ( $cgi->param('quotationnum') =~ /^(\d+)$/ ) {
+ $quotationnum = $1;
+}
+
+die 'no custnum or prospectnum' unless $cust_main || $prospect_main;
my $part_pkg = '';
if ( $cgi->param('lock_pkgpart') ) {
$part_pkg = qsearchs({
'table' => 'part_pkg',
'hashref' => { 'pkgpart' => scalar($cgi->param('lock_pkgpart')) },
- 'extra_sql' => ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ),
+ 'extra_sql' => ' AND '. FS::part_pkg->agent_pkgs_sql(
+ $cust_main ? $cust_main->agent
+ : $prospect_main->agent
+ ),
})
or die "unknown pkgpart ". $cgi->param('lock_pkgpart');
}
my $pkgpart = $part_pkg ? $part_pkg->pkgpart : scalar($cgi->param('pkgpart'));
+my $quantity = 1;
+if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
+ $quantity = $1;
+}
+
my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi?
my $start_date = '';
-if( ! $conf->exists('order_pkg-no_start_date') ) {
+if( ! $conf->exists('order_pkg-no_start_date') && $cust_main ) {
$start_date = $cust_main->next_bill_date;
$start_date = $start_date ? time2str($format, $start_date) : '';
}
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
index 093494a06..5b9f63dc0 100644
--- a/httemplate/misc/payment.cgi
+++ b/httemplate/misc/payment.cgi
@@ -9,67 +9,21 @@
<& /elements/init_overlib.html &>
<% ntable('#cccccc') %>
- <TR>
- <TH ALIGN="right"><% mt('Payment amount') |h %></TH>
- <TD COLSPAN=7>
- <TABLE><TR><TD BGCOLOR="#ffffff">
- <% $money_char %><INPUT NAME = "amount"
- ID = "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>
-% }
-
-<& /elements/tr-select-discount_term.html,
- 'custnum' => $custnum,
- 'amount_id' => 'amount',
-&>
+ <& /elements/tr-amount_fee.html,
+ 'amount' => $amount,
+ 'process-pkgpart' =>
+ scalar($conf->config('manual_process-pkgpart', $cust_main->agentnum)),
+ 'process-display' => scalar($conf->config('manual_process-display')),
+ 'process-skip_first' => $conf->exists('manual_process-skip_first'),
+ 'num_payments' => scalar($cust_main->cust_pay),
+ 'surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')),
+ &>
+
+ <& /elements/tr-select-discount_term.html,
+ 'custnum' => $custnum,
+ 'amount_id' => 'amount',
+ &>
% if ( $payby eq 'CARD' ) {
%
@@ -125,7 +79,7 @@
</TR>
<& /elements/location.html,
- 'object' => $cust_main, #XXX errors???
+ 'object' => $cust_main->bill_location,
'no_asterisks' => 1,
'address1_label' => emt('Card billing address'),
&>
@@ -298,14 +252,16 @@ my $custnum = $1;
my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } );
die "unknown custnum $custnum" unless $cust_main;
+my $location = $cust_main->bill_location;
+# no proper error handling on this anyway, but when we have it,
+# remember to repopulate fields in $location
+
my $balance = $cust_main->balance;
my $payinfo = '';
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', {
@@ -313,42 +269,9 @@ my %states = map { $_->state => 1 }
} );
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';
-
- my $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage');
- $amount += $amount * $cc_surcharge_pct/100 if $cc_surcharge_pct > 0;
-
- $amount = sprintf("%.2f", $amount);
}
my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32;
diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi
index a6b90ea74..1105af943 100644
--- a/httemplate/misc/process/batch-cust_pay.cgi
+++ b/httemplate/misc/process/batch-cust_pay.cgi
@@ -1,51 +1,69 @@
-% die "access denied"
-% unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
-%
-% my $param = $cgi->Vars;
-%
-% #my $paybatch = $param->{'paybatch'};
-% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
-%
-% my @cust_pay = ();
-% #my $row = 0;
-% #while ( exists($param->{"custnum$row"}) ) {
-% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
-% my $custnum = $param->{"custnum$row"};
-% my $cust_main;
-% if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
-% $cust_main = qsearchs({
-% 'table' => 'cust_main',
-% 'hashref' => { 'custnum' => $1 },
-% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-% });
-% }
-% if ( length($custnum) and !$cust_main ) { # not found, try agent_custid
-% $cust_main = qsearchs({
-% 'table' => 'cust_main',
-% 'hashref' => { 'agent_custid' => $custnum },
-% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-% });
-% }
-% $custnum = $cust_main->custnum if $cust_main;
-% # if !$cust_main, then this will throw an error on batch_insert
-%
-% push @cust_pay, new FS::cust_pay {
-% 'custnum' => $custnum,
-% 'paid' => $param->{"paid$row"},
-% 'payby' => 'BILL',
-% 'payinfo' => $param->{"payinfo$row"},
-% 'discount_term' => $param->{"discount_term$row"},
-% 'paybatch' => $paybatch,
-% }
-% if $param->{"custnum$row"}
-% || $param->{"paid$row"}
-% || $param->{"payinfo$row"};
-% #$row++;
-% }
-%
-% my @errors = FS::cust_pay->batch_insert(@cust_pay);
-% my $num_errors = scalar(grep $_, @errors);
-%
+<%init>
+my $DEBUG = 0;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
+
+my $param = $cgi->Vars;
+warn Dumper($param) if $DEBUG;
+
+#my $paybatch = $param->{'paybatch'};
+my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+my @cust_pay = ();
+#my $row = 0;
+#while ( exists($param->{"custnum$row"}) ) {
+for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+ my $custnum = $param->{"custnum$row"};
+ my $cust_main;
+ if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $1 },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+ }
+ if ( length($custnum) and !$cust_main ) { # not found, try agent_custid
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'agent_custid' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ });
+ }
+ $custnum = $cust_main->custnum if $cust_main;
+ # if !$cust_main, then this will throw an error on batch_insert
+
+ my $cust_pay = new FS::cust_pay {
+ 'custnum' => $custnum,
+ 'paid' => $param->{"paid$row"},
+ 'payby' => 'BILL',
+ 'payinfo' => $param->{"payinfo$row"},
+ 'discount_term' => $param->{"discount_term$row"},
+ 'paybatch' => $paybatch,
+ }
+ if $param->{"custnum$row"}
+ || $param->{"paid$row"}
+ || $param->{"payinfo$row"};
+ next if !$cust_pay;
+ #$row++;
+
+ # payment applications, if any
+ my @cust_bill_pay = ();
+ for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+ next if !$param->{"invnum$row.$app"};
+ push @cust_bill_pay, new FS::cust_bill_pay {
+ 'invnum' => $param->{"invnum$row.$app"},
+ 'amount' => $param->{"amount$row.$app"}
+ };
+ }
+ $cust_pay->set('apply_to', \@cust_bill_pay) if scalar(@cust_bill_pay) > 0;
+
+ push @cust_pay, $cust_pay;
+
+}
+
+my @errors = FS::cust_pay->batch_insert(@cust_pay);
+my $num_errors = scalar(grep $_, @errors);
+</%init>
% if ( $num_errors ) {
%
% $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : '').
@@ -65,4 +83,3 @@
%
<% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %>
% }
-
diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
index 662a77670..a106b845a 100755
--- a/httemplate/misc/process/cancel_pkg.html
+++ b/httemplate/misc/process/cancel_pkg.html
@@ -6,19 +6,21 @@
</HTML>
<%once>
-my %past = ( 'cancel' => 'cancelled',
- 'expire' => 'expired',
- 'suspend' => 'suspended',
- 'adjourn' => 'adjourned',
- 'resume' => 'scheduled to resume',
+my %past = ( 'cancel' => 'cancelled',
+ 'expire' => 'expired',
+ 'suspend' => 'suspended',
+ 'adjourn' => 'adjourned',
+ 'resume' => 'scheduled to resume',
+ 'uncancel' => 'un-cancelled',
);
#i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html
-my %right = ( 'cancel' => 'Cancel customer package immediately',
- 'expire' => 'Cancel customer package later',
- 'suspend' => 'Suspend customer package',
- 'adjourn' => 'Suspend customer package later',
- 'resume' => 'Unsuspend customer package', #later?
+my %right = ( 'cancel' => 'Cancel customer package immediately',
+ 'expire' => 'Cancel customer package later',
+ 'suspend' => 'Suspend customer package',
+ 'adjourn' => 'Suspend customer package later',
+ 'resume' => 'Unsuspend customer package', #later?
+ 'uncancel' => 'Un-cancel customer package',
);
</%once>
@@ -26,7 +28,8 @@ my %right = ( 'cancel' => 'Cancel customer package immediately',
#untaint method
my $method = $cgi->param('method');
-$method =~ /^(cancel|expire|suspend|adjourn|resume)$/ or die "Illegal method";
+$method =~ /^(cancel|expire|suspend|adjourn|resume|uncancel)$/
+ or die "Illegal method";
$method = $1;
my $past_method = $past{$method};
@@ -39,7 +42,7 @@ $pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum";
$pkgnum = $1;
my $date = time;
-if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){
+if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
#untaint date
$date = $cgi->param('date'); #huh?
parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
@@ -49,17 +52,22 @@ if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){
$method = 'unsuspend' if $method eq 'resume';
}
-my $resume_date;
+my $resume_date = '';
+my $options = '';
if ( $method eq 'suspend' ) { #or 'adjourn'
$resume_date = parse_datetime($cgi->param('resume_date'))
if $cgi->param('resume_date');
+
+ $options = { map { $_ => scalar($cgi->param($_)) }
+ qw( suspend_bill no_suspend_bill )
+ };
}
my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
#untaint reasonnum
my $reasonnum = $cgi->param('reasonnum');
-if ( $method ne 'unsuspend' ) { #i.e. 'resume'
+if ( $method !~ /^(unsuspend|uncancel)$/ ) {
$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
$reasonnum = $1;
@@ -71,9 +79,22 @@ if ( $method ne 'unsuspend' ) { #i.e. 'resume'
}
}
+#for uncancel
+my $last_bill =
+ $cgi->param('last_bill') ? parse_datetime($cgi->param('last_bill')) : '';
+my $bill =
+ $cgi->param('bill') ? parse_datetime($cgi->param('bill')) : '';
+
+my $svc_fatal = ( $cgi->param('svc_not_fatal') ne 'Y' );
+
my $error = $cust_pkg->$method( 'reason' => $reasonnum,
'date' => $date,
- 'resume_date' => $resume_date );
+ 'resume_date' => $resume_date,
+ 'last_bill' => $last_bill,
+ 'bill' => $bill,
+ 'svc_fatal' => $svc_fatal,
+ 'options' => $options,
+ );
if ($error) {
$cgi->param('error', $error);
diff --git a/httemplate/misc/process/void-cust_bill.html b/httemplate/misc/process/void-cust_bill.html
new file mode 100755
index 000000000..899901a50
--- /dev/null
+++ b/httemplate/misc/process/void-cust_bill.html
@@ -0,0 +1,26 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(1). "void-cust_bill.html?". $cgi->query_string ) %>
+%} else {
+<& /elements/header-popup.html, 'Invoice voided' &>
+<SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+</SCRIPT>
+</BODY></HTML>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Void invoices');
+
+#untaint invnum
+$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum";
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+
+my $custnum = $cust_bill->custnum;
+
+my $error = $cust_bill->void( $cgi->param('reason') );
+
+</%init>
diff --git a/httemplate/misc/suspend_cust.html b/httemplate/misc/suspend_cust.html
new file mode 100644
index 000000000..b41f36f8c
--- /dev/null
+++ b/httemplate/misc/suspend_cust.html
@@ -0,0 +1,79 @@
+<& /elements/header-popup.html, mt('Suspend customer') &>
+
+<& /elements/error.html &>
+
+<FORM NAME="cust_suspend_popup" ACTION="<% popurl(1) %>cust_main-suspend.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <P ALIGN="center"><B><% mt('Suspend this customer?') |h %></B>
+
+<TABLE BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED />
+ </TD>
+ <TD ALIGN="left"><% mt('Suspend now') |h %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" />
+ </TD>
+ <TD ALIGN="left"><% mt('Suspend on date: ') |h %>
+ <& /elements/input-date-field.html, {
+ 'name' => 'adjourn',
+ 'value' => time,
+ } &>
+ </TD>
+</TR>
+</TABLE>
+<SCRIPT type="text/javascript">
+function toggle(val) {
+ document.getElementById("adjourn_text").disabled = !val;
+ document.getElementById("adjourn_button").style.visibility =
+ val ? 'visible' : 'hidden';
+}
+toggle(false);
+</SCRIPT>
+
+<TABLE BGCOLOR="#cccccc", BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'C',
+ 'cgi' => $cgi,
+ 'control_button' => "document.getElementById('confirm_suspend_cust_button')",
+&>
+
+</TABLE>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" ID="confirm_suspend_cust_button" VALUE="<% mt('Suspend customer') |h %>" DISABLED>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<INPUT TYPE="BUTTON" VALUE="<% mt("Don't suspend") |h %>" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#false laziness w/cancel_cust.html
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Suspend customer');
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+die "No customer # $custnum" unless $cust_main;
+
+</%init>
+
diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html
index 672fad8d6..e4392825c 100755
--- a/httemplate/misc/timeworked.html
+++ b/httemplate/misc/timeworked.html
@@ -99,8 +99,6 @@ my(%ticketmap, %ticket, %customers);
my $title = 'Assign Time Worked';
tie %ticketmap, 'Tie::IxHash';
-RT::Init();
-
my $CurrentUser = RT::CurrentUser->new();
$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
diff --git a/httemplate/misc/unsuspend_cust.html b/httemplate/misc/unsuspend_cust.html
new file mode 100644
index 000000000..600eb268a
--- /dev/null
+++ b/httemplate/misc/unsuspend_cust.html
@@ -0,0 +1,68 @@
+<& /elements/header-popup.html, mt('Unsuspend customer') &>
+
+<& /elements/error.html &>
+
+<FORM NAME="cust_unsuspend_popup" ACTION="<% popurl(1) %>cust_main-unsuspend.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+ <P ALIGN="center"><B><% mt('Unsuspend this customer?') |h %></B>
+
+<TABLE BORDER="0" CELLSPACING="2"
+STYLE="margin-left:auto; margin-right:auto">
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED />
+ </TD>
+ <TD ALIGN="left"><% mt('Unsuspend now') |h %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" />
+ </TD>
+ <TD ALIGN="left"><% mt('Unsuspend on date: ') |h %>
+ <& /elements/input-date-field.html, {
+ 'name' => 'resume',
+ 'value' => time,
+ } &>
+ </TD>
+</TR>
+</TABLE>
+<SCRIPT type="text/javascript">
+function toggle(val) {
+ document.getElementById("resume_text").disabled = !val;
+ document.getElementById("resume_button").style.visibility =
+ val ? 'visible' : 'hidden';
+}
+toggle(false);
+</SCRIPT>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" ID="confirm_unsuspend_cust_button" VALUE="<% mt('Unsuspend customer') |h %>">
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<INPUT TYPE="BUTTON" VALUE="<% mt("Don't unsuspend") |h %>" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+#false laziness w/cancel_cust.html
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Unsuspend customer');
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+} );
+die "No customer # $custnum" unless $cust_main;
+
+</%init>
+
diff --git a/httemplate/misc/unvoid-cust_bill_void.html b/httemplate/misc/unvoid-cust_bill_void.html
new file mode 100755
index 000000000..f61416549
--- /dev/null
+++ b/httemplate/misc/unvoid-cust_bill_void.html
@@ -0,0 +1,25 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+% my $show = $curuser->default_customer_view =~ /^(jumbo|payment_history)$/
+% ? ''
+% : ';show=payment_history';
+<% $cgi->redirect($p. "view/cust_main.cgi?custnum=$custnum$show" ) %>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Unvoid invoices');
+
+#untaint invnum
+$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum";
+my $invnum = $1;
+
+my $cust_bill_void = qsearchs('cust_bill_void', { 'invnum' => $invnum } );
+my $custnum = $cust_bill_void->custnum;
+
+my $error = $cust_bill_void->unvoid;
+
+</%init>
diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi
index 91fe1c223..4726ee576 100755
--- a/httemplate/misc/unvoid-cust_pay_void.cgi
+++ b/httemplate/misc/unvoid-cust_pay_void.cgi
@@ -6,7 +6,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Unvoid');
+ unless $FS::CurrentUser::CurrentUser->access_right('Unvoid payments');
#untaint paynum
my($query) = $cgi->keywords;
diff --git a/httemplate/misc/void-cust_bill.html b/httemplate/misc/void-cust_bill.html
new file mode 100644
index 000000000..1608fd051
--- /dev/null
+++ b/httemplate/misc/void-cust_bill.html
@@ -0,0 +1,45 @@
+<& /elements/header-popup.html, mt('Void invoice') &>
+
+<% include('/elements/error.html') %>
+
+<% emt('Are you sure you want to void this invoice?') %>
+<BR><BR>
+
+<% emt("Invoice #[_1] ([_2])",$cust_bill->display_invnum, $money_char. $cust_bill->owed) %>
+<BR><BR>
+
+<FORM METHOD="POST" ACTION="process/void-cust_bill.html">
+<INPUT TYPE="hidden" NAME="invnum" VALUE="<% $invnum %>">
+
+<% ntable("#cccccc", 2) %>
+<TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD><INPUT TYPE="text" NAME="reason" VALUE="<% $cgi->param('reason') |h %>"></TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<CENTER>
+<BUTTON TYPE="submit">Yes, void invoice</BUTTON>&nbsp;&nbsp;&nbsp;\
+<BUTTON TYPE="button" onClick="parent.cClick();">No, do not void invoice</BUTTON>
+</CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Void invoices');
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+#untaint invnum
+$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum";
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+
+</%init>
diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi
index 7b484e93e..31b7a6201 100755
--- a/httemplate/misc/void-cust_pay.cgi
+++ b/httemplate/misc/void-cust_pay.cgi
@@ -12,7 +12,7 @@ my $paynum = $1;
my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum});
-my $right = 'Regular void';
+my $right = 'Void payments';
$right = 'Credit card void' if $cust_pay->payby eq 'CARD';
$right = 'Echeck void' if $cust_pay->payby eq 'CHEK';
diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html
new file mode 100644
index 000000000..46f15d1ab
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_bill-search.html
@@ -0,0 +1,18 @@
+<% encode_json(\@return) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die 'access denied' unless $curuser->access_right('View invoices');
+my @return;
+if ( $cgi->param('sub') eq 'custnum_search_open' ) {
+ my $custnum = $cgi->param('arg');
+ #warn "searching invoices for $custnum\n";
+ my $cust_main = FS::cust_main->by_key($custnum);
+ @return = map {
+ +{ $_->hash,
+ 'owed' => $_->owed }
+ } $cust_main->open_cust_bill
+ if $curuser->agentnums_href->{ $cust_main->agentnum };
+}
+
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
index 436501e8b..acf7e70e2 100644
--- a/httemplate/misc/xmlhttp-cust_main-search.cgi
+++ b/httemplate/misc/xmlhttp-cust_main-search.cgi
@@ -1,18 +1,26 @@
% if ( $sub eq 'custnum_search' ) {
% my $custnum = $cgi->param('arg');
% my $return = [];
-% if ( $custnum =~ /^(\d+)$/ ) {
-% $return = findbycustnum($1,0);
-% $return = findbycustnum($1,1) if(!scalar(@$return));
+% if ( $custnum =~ /^(\d+)$/ ) { #should also handle
+% # cust_main-agent_custid-format') eq 'ww?d+'
+% $return = findbycustnum_or_agent_custid($1);
% }
<% objToJson($return) %>
% } elsif ( $sub eq 'smart_search' ) {
%
% my $string = $cgi->param('arg');
% my @cust_main = smart_search( 'search' => $string,
-% 'no_fuzzy_on_exact' => 1, #pref?
+% 'no_fuzzy_on_exact' => ! $FS::CurrentUser::CurrentUser->option('enable_fuzzy_on_exact'),
% );
-% my $return = [ map [ $_->custnum, $_->name, $_->balance, $_->ucfirst_status, $_->statuscolor ], @cust_main ];
+% my $return = [ map [ $_->custnum,
+% $_->name,
+% $_->balance,
+% $_->ucfirst_status,
+% $_->statuscolor,
+% scalar($_->open_cust_bill)
+% ],
+% @cust_main
+% ];
%
<% objToJson($return) %>
% } elsif ( $sub eq 'invnum_search' ) {
@@ -20,7 +28,7 @@
% my $string = $cgi->param('arg');
% if ( $string =~ /^(\d+)$/ ) {
% my $inv = qsearchs('cust_bill', { 'invnum' => $1 });
-% my $return = $inv ? findbycustnum($inv->custnum,0) : [];
+% my $return = $inv ? findbycustnum($inv->custnum) : [];
<% objToJson($return) %>
% } else { #return nothing
[]
@@ -43,22 +51,58 @@
% }
<%init>
-my $conf = new FS::Conf;
-
my $sub = $cgi->param('sub');
-sub findbycustnum{
- my $custnum = shift;
- my $agent = shift;
- my $hashref = { 'custnum' => $custnum };
- $hashref = { 'agent_custid' => $custnum } if $agent;
- my $c = qsearchs({
- 'table' => 'cust_main',
- 'hashref' => $hashref,
- 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
- });
- return [ $c->custnum, $c->name, $c->balance, $c->ucfirst_status, $c->statuscolor ]
- if $c;
- [];
+sub findbycustnum {
+
+ my $c = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => shift },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or return [];
+
+ [ $c->custnum,
+ $c->name,
+ $c->balance,
+ $c->ucfirst_status,
+ $c->statuscolor,
+ scalar($c->open_cust_bill)
+ ];
+}
+
+sub findbycustnum_or_agent_custid {
+ my $num = shift;
+
+ my @or = ( 'agent_custid = ?' );
+ my @param = ( $num );
+
+ if ( $num =~ /^\d+$/ && $num <= 2147483647 ) { #need a bigint custnum? wow
+ my $conf = new FS::Conf;
+ if ( $conf->exists('cust_main-default_agent_custid') ) {
+ push @or, "( agent_custid IS NULL AND custnum = $num )";
+ } else {
+ push @or, "custnum = $num";
+ }
+ }
+
+ my $extra_sql = ' WHERE '. $FS::CurrentUser::CurrentUser->agentnums_sql.
+ ' AND ( '. join(' OR ', @or). ' )';
+
+ [ map [ $_->custnum,
+ $_->name,
+ $_->balance,
+ $_->ucfirst_status,
+ $_->statuscolor,
+ scalar($_->open_cust_bill),
+ ],
+
+ qsearch({
+ 'table' => 'cust_main',
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'extra_param' => \@param,
+ })
+ ];
}
+
</%init>
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index 974b96dc2..c4fef0311 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -48,8 +48,10 @@ unless ( $error ) { # if ($access_user) {
my %param = $access_user->options;
#XXX autogen
- my @paramlist = qw( locale menu_position default_customer_view mobile_menu
- disable_html_editor
+ my @paramlist = qw( locale menu_position default_customer_view
+ spreadsheet_format mobile_menu
+ enable_fuzzy_on_exact
+ disable_html_editor disable_enter_submit_onetimecharge
email_address
snom-ip snom-username snom-password
vonage-fromnumber vonage-username vonage-password
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 8fd1eaa73..575b8045b 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -32,7 +32,7 @@ Interface
<TR>
<TH ALIGN="right">Locale: </TH>
- <TD>
+ <TD COLSPAN=2>
<SELECT NAME="locale">
% foreach my $locale ( FS::Locales->locales ) {
% my %info = FS::Locales->locale_info($locale);
@@ -75,7 +75,29 @@ Interface
</SELECT>
</TD>
</TR>
-
+
+ <TR>
+ <TH ALIGN="right">Spreadsheet download format: </TH>
+ <TD COLSPAN=2>
+ <SELECT NAME="spreadsheet_format">
+% my $xls = $curuser->option('spreadsheet_format') eq 'XLS';
+% my $xlsx = $curuser->option('spreadsheet_format') eq 'XLSX';
+ <OPTION VALUE=""></OPTION>
+ <OPTION VALUE="XLS"<% $xls ? 'SELECTED' : '' %>>XLS (Excel 97/2000/XP)
+ </OPTION>
+ <OPTION VALUE="XLSX"<% $xlsx ? 'SELECTED' : ''%>>XLSX (Excel 2007+)
+ </OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching even when an exact match is found: </TH>
+ <TD ALIGN="left" COLSPAN=2>
+ <INPUT TYPE="checkbox" NAME="enable_fuzzy_on_exact" VALUE="1" <% $curuser->option('enable_fuzzy_on_exact') ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+
<TR>
<TH ALIGN="right" COLSPAN=1>Disable HTML editor for customer notes: </TH>
<TD ALIGN="left" COLSPAN=2>
@@ -83,6 +105,13 @@ Interface
</TD>
</TR>
+ <TR>
+ <TH ALIGN="right" COLSPAN=1>Disable submission on [Enter] key - one-time charges: </TH>
+ <TD ALIGN="left" COLSPAN=2>
+ <INPUT TYPE="checkbox" NAME="disable_enter_submit_onetimecharge" VALUE="1" <% $curuser->option('disable_enter_submit_onetimecharge') ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+
</TABLE>
<BR>
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
index 250e71811..6f5fcdf3b 100755
--- a/httemplate/search/477.html
+++ b/httemplate/search/477.html
@@ -1,33 +1,24 @@
-% unless ( $type eq 'xml' ) {
-<% include( '/elements/header.html', 'FCC Form 477 Results') %>
-%}else{
+% if ( $type eq 'xml' ) {
<?xml version="1.0" encoding="ISO-8859-1"?>
<Form_477_submission xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://specialreports.fcc.gov/wcb/Form477/XMLSchema-instance/form_477_upload_Schema.xsd" >
-%}
-% if ( $type eq 'html' || $type eq 'html-print' ) {
+% } else { #html
+<& /elements/header.html, "FCC Form 477 Results - $state" &>
<TABLE WIDTH="100%">
- <TR><TD></TD>
-%}elsif ( $type eq 'xml' ) {
-%}
-% unless ( $type eq 'html-print' || $type eq 'xml' ) {
+ <TR>
+ <TD></TD>
+ <TD ALIGN="right" CLASS="noprint">
+ Download full results<BR>
+% $cgi->param('_type', 'xml');
+ as <A HREF="<% $cgi->self_url %>">XML file</A><BR>
- <TD ALIGN="right">
+% $cgi->param('_type', 'html-print');
+ as <A HREF="<% $cgi->self_url %>">printable copy</A>
- Download full results<BR>
-% $cgi->param('_type', 'xml');
- as <A HREF="<% $cgi->self_url %>">XML file</A><BR>
-
-% $cgi->param('_type', 'html-print');
- as <A HREF="<% $cgi->self_url %>">printable copy</A>
-
- </TD>
-% $cgi->param('_type', $type );
-% }
-% if ( $type eq 'html' || $type eq 'html-print' ) {
+ </TD>
+% $cgi->param('_type', $type );
</TR>
</TABLE>
-%}elsif ( $type eq 'xml' ) {
-%}
+% } #html
% foreach my $part ( @parts ) {
% if ( $part{$part} ) {
%
@@ -47,8 +38,8 @@
% if ( $type eq 'xml' ) {
<<% 'Part_IA_'. chr(65 + $tech) %>>
% }
-<% include( "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url ) %>
-<% include( "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url ) %>
+<& "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url &>
+<& "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url &>
% if ( $type eq 'xml' ) {
</<% 'Part_IA_'. chr(65 + $tech) %>>
% }
@@ -58,7 +49,7 @@
<<% 'Part_'. $part %>>
% }
% my $url = &{$url_mangler}($part);
-<% include( "477part${part}.html", 'url' => $url ) %>
+<& "477part${part}.html", 'url' => $url &>
% if ( $type eq 'xml' ) {
</<% 'Part_'. $part %>>
% }
@@ -66,11 +57,11 @@
% }
% }
%
-% if ( $type eq 'html' || $type eq 'html-print' ) {
-<% include( '/elements/footer.html') %>
-%}elsif ( $type eq 'xml' ) {
+% if ( $type eq 'xml' ) {
</Form_477_submission>
-%}
+% } else {
+<& /elements/footer.html &>
+% }
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -78,6 +69,9 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right('List packages');
+my $state = uc($cgi->param('state'));
+$state =~ /^[A-Z]{2}$/ or die "illegal state: $state";
+
my %part = map { $_ => 1 } grep { /^\w+$/ } $cgi->param('part');
my $type = $cgi->param('_type') || 'html';
my $xlsname = '477report';
diff --git a/httemplate/search/477partIA_detail.html b/httemplate/search/477partIA_detail.html
index 2eca1072b..66f3a8651 100755
--- a/httemplate/search/477partIA_detail.html
+++ b/httemplate/search/477partIA_detail.html
@@ -23,9 +23,10 @@ die "access denied"
my %opt = @_;
my %search_hash = ();
-for ( qw(agentnum magic) ) {
+for ( qw(agentnum magic state) ) {
$search_hash{$_} = $cgi->param($_) if $cgi->param($_);
}
+$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ];
diff --git a/httemplate/search/477partIA_summary.html b/httemplate/search/477partIA_summary.html
index ecacaefad..f5c2bc251 100755
--- a/httemplate/search/477partIA_summary.html
+++ b/httemplate/search/477partIA_summary.html
@@ -40,9 +40,10 @@ die "access denied"
my %opt = @_;
my %search_hash = ();
-for ( qw(agentnum magic) ) {
+for ( qw(agentnum magic state) ) {
$search_hash{$_} = $cgi->param($_) if $cgi->param($_);
}
+$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ];
my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option')
diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html
index 9b363ad5e..d2cc8c3e9 100755
--- a/httemplate/search/477partIIA.html
+++ b/httemplate/search/477partIIA.html
@@ -22,9 +22,10 @@ die "access denied"
my $html_init = '<H2>Part IIA</H2>';
my %search_hash = ();
-for ( qw(agentnum magic) ) {
+for ( qw(agentnum magic state) ) {
$search_hash{$_} = $cgi->param($_) if $cgi->param($_);
}
+$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ];
my @row_option = grep { /^\d+$/ } $cgi->param('part2a_row_option')
diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html
index 94aa818fb..c58310d36 100755
--- a/httemplate/search/477partIIB.html
+++ b/httemplate/search/477partIIB.html
@@ -1,17 +1,44 @@
-<% include( 'elements/search.html',
- 'html_init' => $html_init,
- 'name' => 'lines',
- 'query' => $query,
- 'count_query' => 'SELECT 11',
- 'really_disable_download' => 1,
- 'disable_download' => 1,
- 'nohtmlheader' => 1,
- 'disable_total' => 1,
- 'header' => [ @headers ],
- 'xml_elements' => [ @xml_elements ],
- 'fields' => [ @fields ],
- )
-%>
+% if ( $cgi->param('_type') eq 'xml' ) {
+% my @cols = qw(a b c);
+% for ( my $row = 0; $row < scalar(@rows); $row++ ) {
+% for my $col (0..2) {
+% if ( exists($data[$col][$row]) ) {
+<PartII_<% $row %><% $cols[$col] %>>
+% }
+</PartII_<% $row %><% $cols[$col] %>>
+% } #for $col
+% } #for $row
+% } else { # HTML mode
+% # fake up the search-html.html header
+<H2>Part IIB</H2>
+<TABLE>
+ <TR><TD VALIGN="bottom"><BR></TD></TR>
+ <TR><TD COLSPAN=2>
+ <TABLE CLASS="grid" CELLSPACING=0 STYLE="border: 1px solid #cccccc;" BGCOLOR="#cccccc">
+ <TR>
+% foreach (@headers) {
+ <TH class="grid"><% $_ %></TH>
+% }
+ </TR>
+% my @bgcolor = ('eeeeee','ffffff');
+% my $row = 0;
+% foreach my $rowhead (@rows) {
+ <TR>
+ <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"><% $rowhead %></TD>
+% for my $col (0..2) {
+ <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>">
+% if ( exists($data[$col][$row]) ) {
+ <% $data[$col][$row] %>
+% }
+ </TD>
+% } # for $col
+ </TR>
+% $row++;
+% } #for $rowhead
+ </TABLE>
+ </TD></TR>
+</TABLE>
+% } #XML/HTML
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -19,67 +46,89 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right('List packages');
-my $html_init = '<H2>Part IIB</H2>';
my %search_hash = ();
-
-for ( qw(agentnum magic) ) {
- $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
-}
-$search_hash{'classnum'} = [ $cgi->param('classnum') ];
-
-my @row_option = grep { /^\d+$/ } $cgi->param('part2b_row_option')
- if $cgi->param('part2b_row_option');
-
-# fudge in 2nd row
-unshift @row_option, $row_option[0];
-
-my $query = 'SELECT '. join(' UNION SELECT ', 1..8);
-
-my $total_count = 0;
-my $column_value = sub {
- my $row = shift;
-
- my @report_option = ( $row_option[$row - 1] || '' );
- my $sql_query = FS::cust_pkg->search(
- { %search_hash, 'report_option' => join(',', @report_option) }
- );
-
- my $count_sql = delete($sql_query->{'count_query'});
- if ( $row == 2 ) {
- $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END ELSE 0 END, 0) ) FROM/
- or die "couldn't parse count_sql";
- } else {
- $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END, 0)) FROM/
- or die "couldn't parse count_sql";
- }
-
- my $count_sth = dbh->prepare($count_sql)
- or die "Error preparing $count_sql: ". dbh->errstr;
- $count_sth->execute
- or die "Error executing $count_sql: ". $count_sth->errstr;
- my $count_arrayref = $count_sth->fetchrow_arrayref;
- my $count = $count_arrayref->[0];
+$search_hash{'agentnum'} = $cgi->param('agentnum');
+$search_hash{'state'} = $cgi->param('state');
+$search_hash{'classnum'} = [ $cgi->param('classnum') ];
+$search_hash{'status'} = 'active';
- $total_count = $count if $row == 1;
- $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0)
- if $row != 1;
+my @row_option;
+foreach ($cgi->param('part2b_row_option')) {
+ push @row_option, (/^\d+$/ ? $_ : undef);
+}
- return "$count";
+my $is_residential = "AND COALESCE(cust_main.company, '') = ''";
+my $has_report_option = sub {
+ map {
+ defined($row_option[$_]) ?
+ "AND EXISTS(
+ SELECT 1 FROM part_pkg_option
+ WHERE part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND optionname = 'report_option_" . $row_option[$_]."'
+ AND optionvalue = '1'
+ )" : 'AND FALSE'
+ } @_
};
-my @headers = (
- '',
- 'without broadband',
- 'with broadband',
- 'wholesale',
+# an arrayref for each column
+my @data;
+# get the skeleton of the query
+my $sql_query = FS::cust_pkg->search(\%search_hash);
+my $from_where = $sql_query->{'count_query'};
+$from_where =~ s/^SELECT COUNT\(\*\) //;
+# columns 1 and 2
+my $query_ds0 = "SELECT SUM(COALESCE(part_pkg.fcc_ds0s, pkg_class.fcc_ds0s, 0))
+ $from_where";
+# column 3
+my $query_custnum = "SELECT COUNT(DISTINCT cust_pkg.custnum) $from_where";
+
+my @base_queries = ($query_ds0, $query_ds0, $query_custnum);
+my @col_conds = (
+ # column 1
+ [
+ '',
+ $is_residential,
+ $has_report_option->(0), # nomadic
+ ],
+ # column 2
+ [
+ '',
+ $is_residential,
+ $has_report_option->(0..5),
+ ],
+ # column 3
+ [
+ ''
+ ]
);
-my @xml_elements = (
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" },
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" },
- sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" },
-);
+my $col = 0;
+foreach (@col_conds) {
+ my @col_data;
+ my $row = 0;
+ foreach my $cond (@{ $col_conds[$col] }) {
+ # three parts: the select expression, the VoIP class (column selection),
+ # and the row selection
+ my $query = $base_queries[$col] .
+ " AND part_pkg.fcc_voip_class = '".($col+1)."'
+ $cond";
+ my $count = FS::Record->scalar_sql($query) || 0;
+ if ( $row == 0 ) {
+ $col_data[$row] = $count; # the raw count
+ } else {
+ if ( $col_data[0] == 0 ) {
+ $col_data[$row] = ''; # show nothing in this row, then
+ } else {
+ $col_data[$row] = sprintf('%.2f', 100 * $count / $col_data[0]) . '%';
+ }
+ } #if $row == 0
+ $row++;
+ }
+ $data[$col] = \@col_data;
+ $col++;
+}
+
my @rows = (
'total number',
@@ -92,12 +141,11 @@ my @rows = (
'% other broadband',
);
-my @fields = (
- sub { my $row = shift; $rows[$row->[0] - 1]; },
- sub { 0; },
- sub { my $row = shift; &{$column_value}($row->[0]); },
- sub { 0; },
+my @headers = (
+ '',
+ 'without broadband',
+ 'with broadband',
+ 'wholesale',
);
-shift @fields if $cgi->param('_type') eq 'xml';
</%init>
diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html
index 55ebc0be2..2fd5119d1 100755
--- a/httemplate/search/477partV.html
+++ b/httemplate/search/477partV.html
@@ -10,7 +10,7 @@
'no_field_elements' => 1,
'fields' => [ 'zip' ],
'url' => $opt{url} || '',
- 'disable_download' => 1,
+ 'really_disable_download' => 1,
)
%>
@@ -27,16 +27,19 @@ my %search_hash = ();
my @sql_query = ();
my @count_query = ();
-for ( qw(agentnum magic) ) {
+for ( qw(agentnum magic state) ) {
$search_hash{$_} = $cgi->param($_) if $cgi->param($_);
}
+$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ];
$search_hash{report_option} = $cgi->param('partv_report_option')
if $cgi->param('partv_report_option');
-my $sql_query = FS::cust_pkg->search( { %search_hash, 'fcc_line' => 1 });
-$sql_query->{select} = 'DISTINCT substr(zip,1,5) as zip';
-$sql_query->{order_by} = 'ORDER BY substr(zip,1,5)';
+my $sql_query = FS::cust_pkg->search( { %search_hash,
+ 'fcc_line' => 1,
+ 'select_zip5' => 1,
+ }
+ );
my $count_query = delete($sql_query->{'count_query'});
$count_query =~ s/COUNT\(\*\)/count(DISTINCT substr(zip,1,5))/;
$count_query =~ s/ORDER BY [.\w]+//;
diff --git a/httemplate/search/477partVI_census.html b/httemplate/search/477partVI_census.html
index 4d1fb2136..8425c4b48 100755
--- a/httemplate/search/477partVI_census.html
+++ b/httemplate/search/477partVI_census.html
@@ -23,6 +23,7 @@
'links' => \@links,
'url' => $opt{url} || '',
'xml_row_element' => 'Datarow',
+ 'really_disable_download' => 1,
)
%>
<%init>
@@ -80,9 +81,10 @@ push @fields,
my %search_hash = ();
my @sql_query = ();
-for ( qw(agentnum magic) ) {
+for ( qw(agentnum magic state) ) {
$search_hash{$_} = $cgi->param($_) if $cgi->param($_);
}
+$search_hash{'country'} = 'US';
$search_hash{'classnum'} = [ $cgi->param('classnum') ]
if grep { $_ eq 'classnum' } $cgi->param;
@@ -115,10 +117,10 @@ foreach my $row ( @row_option ) {
);
my $extracolumns = "$rowcount AS upload, $columncount AS download, $tech_code as technology_code";
my $percent = "CASE WHEN count(*) > 0 THEN 100-100*cast(count(cust_main.company) as numeric)/cast(count(*) as numeric) ELSE cast(0 as numeric) END AS residential";
- $sql_query->{select} = "count(*) AS quantity, $extracolumns, censustract, $percent";
+ $sql_query->{select} = "count(*) AS quantity, $extracolumns, cust_location.censustract, $percent";
$sql_query->{order_by} =~ /^(.*)(ORDER BY pkgnum)(.*)$/s
or die "couldn't parse order_by";
- $sql_query->{order_by} = "$1 GROUP BY censustract $3";
+ $sql_query->{order_by} = "$1 GROUP BY cust_location.censustract $3";
push @sql_query, $sql_query;
}
$columncount++;
@@ -131,7 +133,8 @@ my $count_query = 'SELECT count(*) FROM ( ('.
map { my $addl_from = $_->{addl_from};
my $extra_sql = $_->{extra_sql};
my $order_by = $_->{order_by};
- "SELECT censustract from cust_pkg $addl_from $extra_sql $order_by";
+ "SELECT cust_location.censustract from cust_pkg $addl_from
+ $extra_sql $order_by";
}
@sql_query
). ') ) AS foo';
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
index 813f9b843..406486a85 100755
--- a/httemplate/search/cust_bill.html
+++ b/httemplate/search/cust_bill.html
@@ -93,6 +93,10 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
$search{'agentnum'} = $1;
}
+ if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ $search{'refnum'} = $1;
+ }
+
if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
$search{'custnum'} = $1;
}
diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html
index 1fc8ffd78..22e9a6795 100644
--- a/httemplate/search/cust_bill_pay.html
+++ b/httemplate/search/cust_bill_pay.html
@@ -92,6 +92,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$title = $agent->agent. " $title";
}
+if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @search, "refnum = $1";
+ my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } );
+ die "unknown refnum $1" unless $part_referral;
+ $title = $part_referral->referral. " $title";
+}
+
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
push @search, "cust_bill._date >= $beginning ",
"cust_bill._date <= $ending";
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 94860d3f2..4c0fa4a56 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -3,23 +3,10 @@
'name' => emt('line items'),
'query' => $query,
'count_query' => $count_query,
- 'count_addl' => [ $money_char. '%.2f total',
- $unearned ? ( $money_char. '%.2f unearned revenue' ) : (),
- ],
+ 'count_addl' => \@total_desc,
'header' => [
emt('Description'),
- ( $unearned
- ? ( emt('Unearned'), emt('Owed'), emt('Payment date') )
- : ( emt('Setup charge') )
- ),
- ( $use_usage eq 'usage'
- ? emt('Usage charge')
- : emt('Recurring charge')
- ),
- ( $unearned
- ? ( emt('Charge start'), emt('Charge end') )
- : ()
- ),
+ @peritem_desc,
emt('Invoice'),
emt('Date'),
FS::UI::Web::cust_header(),
@@ -31,66 +18,21 @@
},
#strikethrough or "N/A ($amount)" or something these when
# they're not applicable to pkg_tax search
- sub { my $cust_bill_pkg = shift;
- if ( $unearned ) {
- my $period =
- $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
- my $elapsed = $unearned - $cust_bill_pkg->sdate;
- $elapsed = 0 if $elapsed < 0;
-
- my $remaining = 1 - $elapsed/$period;
-
- sprintf($money_char. '%.2f',
- $remaining * $cust_bill_pkg->recur );
-
- } else {
- sprintf($money_char.'%.2f', $cust_bill_pkg->setup );
- }
- },
- ( $unearned
- ? ( $owed_sub, $payment_date_sub, )
- : ()
- ),
- 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 );
- },
- ( $unearned
- ? ( sub { time2str('%b %d %Y', shift->sdate ) },
- sub { time2str('%b %d %Y', shift->edate ) },
- )
- : ()
- ),
+ @peritem_sub,
'invnum',
sub { time2str('%b %d %Y', shift->_date ) },
\&FS::UI::Web::cust_fields,
],
'sort_fields' => [
'',
- 'setup', #broken in $unearned case i guess
- ( $unearned ? ('', '') : () ),
- ( $use_usage eq 'recurring' ? 'recur - usage' :
- $use_usage eq 'usage' ? 'usage'
- : 'recur'
- ),
- ( $unearned ? ('sdate', 'edate') : () ),
+ @peritem,
'invnum',
'_date',
],
'links' => [
#'',
'',
- '',
- ( $unearned ? ( '', '' ) : () ),
- '',
- ( $unearned ? ( '', '' ) : () ),
+ @peritem_null,
$ilink,
$ilink,
( map { $_ ne 'Cust. Status' ? $clink : '' }
@@ -98,19 +40,14 @@
),
],
#'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
- 'align' => 'lr'.
- ( $unearned ? 'rc' : '' ).
- 'r'.
- ( $unearned ? 'cc' : '' ).
+ 'align' => 'l'.
+ $peritem_align.
'rc'.
FS::UI::Web::cust_aligns(),
'color' => [
#'',
'',
- '',
- ( $unearned ? ( '', '' ) : () ),
- '',
- ( $unearned ? ( '', '' ) : () ),
+ @peritem_null,
'',
'',
FS::UI::Web::cust_colors(),
@@ -118,32 +55,124 @@
'style' => [
#'',
'',
- '',
- ( $unearned ? ( '', '' ) : () ),
- '',
- ( $unearned ? ( '', '' ) : () ),
+ @peritem_null,
'',
'',
FS::UI::Web::cust_styles(),
],
&>
-<%init>
+<%doc>
+
+Output parameters:
+- distribute: Boolean. If true, recurring fees will be "prorated" for the
+ portion of the package date range (sdate-edate) that falls within the date
+ range of the report. Line items will be limited to those for which this
+ portion is > 0. This disables filtering on invoice date.
+
+- use_usage: Separate usage (cust_bill_pkg_detail records) from
+ recurring charges. If set to "usage", will show usage instead of
+ recurring charges. If set to "recurring", will deduct usage and only
+ show the flat rate charge. If not passed, the "recurring charge" column
+ will include usage charges also.
+
+Filtering parameters:
+- begin, end: Date range. Applies to invoice date, not necessarily package
+ date range. But see "distribute".
+
+- status: Customer status (active, suspended, etc.). This will filter on
+ _current_ customer status, not status at the time the invoice was generated.
+
+- agentnum: Filter on customer agent.
+
+- refnum: Filter on customer reference source.
+
+- classnum: Filter on package class.
+
+- use_override: Apply "classnum" and "taxclass" filtering based on the
+ override (bundle) pkgpart, rather than always using the true pkgpart.
+
+- nottax: Limit to items that are not taxes (pkgnum > 0).
+
+- istax: Limit to items that are taxes (pkgnum == 0).
+
+- taxnum: Limit to items whose tax definition matches this taxnum.
+ With "nottax" that means items that are subject to that tax;
+ with "istax" it's the tax charges themselves. Can be specified
+ more than once to include multiple taxes.
+
+- country, state, county, city: Limit to items whose tax location
+ matches these fields. If "nottax" it's the tax location of the package;
+ if "istax" the location of the tax.
+
+- taxname, taxnameNULL: With "nottax", limit to items whose tax location
+ matches a tax with this name. With "istax", limit to items that have
+ this tax name. taxnameNULL is equivalent to "taxname = '' OR taxname
+ = 'Tax'".
+
+- out: With "nottax", limit to items that don't match any tax definition.
+ With "istax", find tax items that are unlinked to their tax definitions.
+ Current Freeside (> July 2012) always creates tax links, but unlinked
+ items may result from an incomplete upgrade of legacy data.
+
+- locationtaxid: With "nottax", limit to packages matching this
+ tax_rate_location ID; with "tax", limit to taxes generated from that
+ location.
+
+- taxclass: Filter on package taxclass.
+
+- taxclassNULL: With "nottax", limit to items that would be subject to the
+ tax with taxclass = NULL. This doesn't necessarily mean part_pkg.taxclass
+ is NULL; it also includes taxclasses that don't have a tax in this region.
-#LOTS of false laziness below w/cust_credit_bill_pkg.cgi
+- itemdesc: Limit to line items with this description. Note that non-tax
+ packages usually have a description of NULL. (Deprecated.)
+
+- report_group: Can contain '=' or '!=' followed by a string to limit to
+ line items where itemdesc starts with, or doesn't start with, the string.
+
+- cust_tax: Limit to customers who are tax-exempt. If "taxname" is also
+ specified, limit to customers who are also specifically exempt from that
+ tax.
+
+- pkg_tax: Limit to packages that are tax-exempt, and only include the
+ exempt portion (setup, recurring, or both) when calculating totals.
+
+- taxable: Limit to packages that are subject to tax, i.e. where a
+ cust_bill_pkg_tax_location record exists.
+
+- credit: Limit to line items that received a credit application. The
+ amount of the credit will also be shown.
+
+</%doc>
+<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
-my $unearned = '';
+my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
+my @total = ( 'COUNT(*)', 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)');
+my @total_desc = ( '%d line items', $money_char.'%.2f total' ); # sprintf strings
+my @peritem = ( 'setup', 'recur' );
+my @peritem_desc = ( 'Setup charge', 'Recurring charge' );
+my ($join_cust, $join_pkg ) = ('', '');
+my $use_usage;
+
+# valid in both the tax and non-tax cases
+$join_cust =
+ " LEFT JOIN cust_bill USING (invnum)
+ LEFT JOIN cust_main USING (custnum)
+ ";
-#here is the agent virtualization
+#agent virtualization
my $agentnums_sql =
$FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
my @where = ( $agentnums_sql );
+# date range
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
if ( $cgi->param('distribute') == 1 ) {
@@ -152,457 +181,407 @@ if ( $cgi->param('distribute') == 1 ) {
;
}
else {
- push @where, "_date >= $beginning",
- "_date <= $ending";
+ push @where, "cust_bill._date >= $beginning",
+ "cust_bill._date <= $ending";
}
+# status
+if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
+ push @where, FS::cust_main->cust_status_sql . " = '$1'";
+}
+
+# agentnum
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 ) {
- $comparison = "IS NULL";
- } else {
- $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";
- }
+# refnum
+if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.refnum = $1";
}
-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, ' part_pkg.taxclass IN ( '.
- join(', ', map dbh->quote($_), $cgi->param('taxclass') ).
- ' ) ';
-
- #}
-
-}
+# the non-tax case
+if ( $cgi->param('nottax') ) {
-my @loc_param = qw( city county state country );
+ push @where, 'cust_bill_pkg.pkgnum > 0';
-if ( $cgi->param('out') ) {
+ # then we want the package and its definition
+ $join_pkg =
+' LEFT JOIN cust_pkg USING (pkgnum)
+ LEFT JOIN part_pkg USING (pkgpart)';
- my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 );
- while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution
- $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e;
+ my $part_pkg = 'part_pkg';
+ if ( $cgi->param('use_override') ) {
+ # still need the real part_pkg for tax applicability,
+ # so alias this one
+ $join_pkg .= " LEFT JOIN part_pkg AS override ON (
+ COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart
+ )";
+ $part_pkg = 'override';
}
+ push @select, 'part_pkg.pkg'; # or should this use override?
- $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g
- if $cgi->param('istax');
-
- push @where, "
- 0 = (
- SELECT COUNT(*) FROM cust_main_county
- WHERE cust_main_county.tax > 0
- AND $loc_sql
- )
- ";
-
- #not linked to by anything, but useful for debugging "out of taxable region"
- if ( grep $cgi->param($_), @loc_param ) {
-
- my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
-
- 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;
+ my @tax_where; # will go into a subquery
+ my @exempt_where; # will also go into a subquery
+ # classnum (of override pkgpart if applicable)
+ # not specified: all classes
+ # 0: empty class
+ # N: classnum
+ if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ push @where, "COALESCE($part_pkg.classnum, 0) = $1";
}
-} elsif ( $cgi->param('country') ) {
-
- my @counties = $cgi->param('county');
-
- if ( scalar(@counties) > 1 ) {
-
- #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( city state country )
- );
+ # taxclass
+ if ( $cgi->param('taxclassNULL') ) {
+ # a little different from 'taxclass' in that it applies to the
+ # effective taxclass, not the real one
+ push @tax_where, 'cust_main_county.taxclass IS NULL'
+ } elsif ( $cgi->param('taxclass') ) {
+ push @tax_where, "$part_pkg.taxclass IN (" .
+ join(', ', map {dbh->quote($_)} $cgi->param('taxclass') ).
+ ')';
+ }
- 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;
- }
+ if ( $cgi->param('exempt_cust') eq 'Y' ) {
+ # tax-exempt customers
+ push @exempt_where, "(exempt_cust = 'Y' OR exempt_cust_taxname = 'Y')";
- $loc_sql;
+ } elsif ( $cgi->param('exempt_pkg') eq 'Y' ) { # non-taxable package
+ # non-taxable package charges
+ push @exempt_where, "(exempt_setup = 'Y' OR exempt_recur = 'Y')";
+ }
+ # we don't handle exempt_monthly here
+
+ if ( $cgi->param('taxname') ) { # specific taxname
+ push @tax_where, 'cust_main_county.taxname = '.
+ dbh->quote($cgi->param('taxname'));
+ } elsif ( $cgi->param('taxnameNULL') ) {
+ push @tax_where, 'cust_main_county.taxname IS NULL OR '.
+ 'cust_main_county.taxname = \'Tax\'';
+ }
- } @counties
+ # country:state:county:city:district (may be repeated)
+ # You can also pass a big list of taxnums but that leads to huge URLs.
+ # Note that this means "packages whose tax is in this region", not
+ # "packages in this region". It's meant for links from the tax report.
+ if ( $cgi->param('region') ) {
+ my @orwhere;
+ foreach ( $cgi->param('region') ) {
+ my %loc;
+ @loc{qw(country state county city district)} =
+ split(':', $cgi->param('region'));
+ my $string = join(' AND ',
+ map {
+ if ( $loc{$_} ) {
+ "$_ = ".dbh->quote($loc{$_});
+ } else {
+ "$_ IS NULL";
+ }
+ } keys(%loc)
+ );
+ push @orwhere, "($string)";
+ }
+ push @tax_where, '(' . join(' OR ', @orwhere) . ')' if @orwhere;
+ }
- ). ' ) ';
+ # specific taxnums
+ if ( $cgi->param('taxnum') ) {
+ my $taxnum_in = join(',',
+ grep /^\d+$/, $cgi->param('taxnum')
+ );
+ push @tax_where, "cust_main_county.taxnum IN ($taxnum_in)"
+ if $taxnum_in;
+ }
- push @where, $locs_sql;
+ # If we're showing exempt items, we need to find those with
+ # cust_tax_exempt_pkg records matching the selected taxes.
+ # If we're showing taxable items, we need to find those with
+ # cust_bill_pkg_tax_location records. We also need to find the
+ # exemption records so that we can show the taxable amount.
+ # If we're showing all items, we need the union of those.
+ # If we're showing 'out' (items that aren't region/class taxable),
+ # then we need the set of all items minus the union of those.
- } else {
+ my $exempt_sub;
- my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param;
+ if ( @exempt_where or @tax_where
+ or $cgi->param('taxable') or $cgi->param('out') )
+ {
+ # process exemption restrictions, including @tax_where
+ my $exempt_sub = 'SELECT SUM(amount) as exempt_amount, billpkgnum
+ FROM cust_tax_exempt_pkg JOIN cust_main_county USING (taxnum)';
- 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;
- }
+ $exempt_sub .= ' WHERE '.join(' AND ', @tax_where, @exempt_where)
+ if (@tax_where or @exempt_where);
- push @where, $loc_sql;
+ $exempt_sub .= ' GROUP BY billpkgnum';
+ $join_pkg .= " LEFT JOIN ($exempt_sub) AS item_exempt
+ USING (billpkgnum)";
}
-
- if ( $cgi->param('istax') ) {
- if ( $cgi->param('taxname') ) {
- push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') );
- #} elsif ( $cgi->param('taxnameNULL') {
- } else {
- push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
- }
- } elsif ( $cgi->param('nottax') ) {
- #what can we usefully do with "taxname" ???? look up a class???
- } else {
- #warn "neither nottax nor istax parameters specified";
- }
-
- if ( $cgi->param('taxclassNULL')
- && ! $cgi->param('istax') #no part_pkg.taxclass in this case
- #(see comment above?)
- )
- {
- my %hash = ( 'country' => scalar($cgi->param('country')) );
- foreach (qw( state county )) {
- $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_);
- }
- my $cust_main_county = qsearchs('cust_main_county', \%hash);
- die "unknown base region for empty taxclass" unless $cust_main_county;
-
- my $same_sql = $cust_main_county->sql_taxclass_sameregion;
- $same_sql =~ s/taxclass/part_pkg.taxclass/g;
- push @where, $same_sql if $same_sql;
-
+
+ if ( @tax_where or $cgi->param('taxable') or $cgi->param('out') ) {
+ # process tax restrictions
+ unshift @tax_where,
+ 'cust_main_county.tax > 0';
+
+ my $tax_sub = "SELECT invnum, cust_bill_pkg_tax_location.pkgnum
+ FROM cust_bill_pkg_tax_location
+ JOIN cust_bill_pkg AS tax_item USING (billpkgnum)
+ JOIN cust_main_county USING (taxnum)
+ WHERE ". join(' AND ', @tax_where).
+ " GROUP BY invnum, cust_bill_pkg_tax_location.pkgnum";
+
+ $join_pkg .= " LEFT JOIN ($tax_sub) AS item_tax
+ ON (item_tax.invnum = cust_bill_pkg.invnum AND
+ item_tax.pkgnum = cust_bill_pkg.pkgnum)";
}
-} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
+ # now do something with that
+ if ( @exempt_where ) {
- push @where, FS::tax_rate_location->location_sql(
- map { $_ => (scalar($cgi->param($_)) || '') }
- qw( city county state locationtaxid )
- );
+ push @where, 'item_exempt.billpkgnum IS NOT NULL';
+ push @select, 'item_exempt.exempt_amount';
+ push @peritem, 'exempt_amount';
+ push @peritem_desc, 'Exempt';
+ push @total, 'SUM(exempt_amount)';
+ push @total_desc, "$money_char%.2f tax-exempt";
-} elsif ( $cgi->param('unearned_now') =~ /^(\d+)$/ ) {
+ } elsif ( $cgi->param('taxable') ) {
- $unearned = $1;
+ my $taxable = 'cust_bill_pkg.setup + cust_bill_pkg.recur '.
+ '- COALESCE(item_exempt.exempt_amount, 0)';
- push @where, "cust_bill_pkg.sdate < $unearned",
- "cust_bill_pkg.edate > $unearned",
- "cust_bill_pkg.recur != 0",
- "part_pkg.freq != '0'",
- "part_pkg.freq != '1'",
- "part_pkg.freq NOT LIKE '%h'",
- "part_pkg.freq NOT LIKE '%d'",
- "part_pkg.freq NOT LIKE '%w'";
+ push @where, 'item_tax.invnum IS NOT NULL';
+ push @select, "($taxable) AS taxable_amount";
+ push @peritem, 'taxable_amount';
+ push @peritem_desc, 'Taxable';
+ push @total, "SUM($taxable)";
+ push @total_desc, "$money_char%.2f taxable";
-}
-
-if ( $cgi->param('itemdesc') ) {
- if ( $cgi->param('itemdesc') eq 'Tax' ) {
- push @where, "(itemdesc='Tax' OR itemdesc is null)";
- } 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";
- }
+ } elsif ( $cgi->param('out') ) {
-}
+ push @where, 'item_tax.invnum IS NULL',
+ 'item_exempt.billpkgnum IS NULL';
-push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax');
-push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax');
-
-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' ";
- }
+ } elsif ( @tax_where ) {
- push @where, $cust_exempt;
-}
+ # union of taxable + all exempt_ cases
+ push @where,
+ '(item_tax.invnum IS NOT NULL OR item_exempt.billpkgnum IS NOT NULL)';
-my $use_usage = $cgi->param('use_usage');
-
-my $count_query;
-if ( $cgi->param('pkg_tax') ) {
-
- $count_query =
- "SELECT COUNT(*),
- SUM(
- ( CASE WHEN part_pkg.setuptax = 'Y'
- THEN cust_bill_pkg.setup
- ELSE 0
- END
- )
- +
- ( CASE WHEN part_pkg.recurtax = 'Y'
- THEN cust_bill_pkg.recur
- ELSE 0
- END
- )
- )
- ";
-
- push @where, "( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
- OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) )",
- "( tax != 'Y' OR tax IS NULL )";
-
-} elsif ( $cgi->param('taxable') ) {
-
- my $setup_taxable = "(
- CASE WHEN part_pkg.setuptax = 'Y'
- THEN 0
- ELSE cust_bill_pkg.setup
- END
- )";
-
- my $recur_taxable = "(
- CASE WHEN part_pkg.recurtax = 'Y'
- THEN 0
- ELSE cust_bill_pkg.recur
- END
- )";
-
- my $exempt = "(
- SELECT COALESCE( SUM(amount), 0 ) FROM cust_tax_exempt_pkg
- WHERE cust_tax_exempt_pkg.billpkgnum = cust_bill_pkg.billpkgnum
- )";
-
- $count_query =
- "SELECT COUNT(*), SUM( $setup_taxable + $recur_taxable - $exempt )";
-
- push @where,
- #not tax-exempt package (setup or recur)
- "(
- ( ( part_pkg.setuptax != 'Y' OR part_pkg.setuptax IS NULL )
- AND cust_bill_pkg.setup > 0 )
- OR
- ( ( part_pkg.recurtax != 'Y' OR part_pkg.recurtax IS NULL )
- AND cust_bill_pkg.recur > 0 )
- )",
- #not a tax_exempt customer
- "( tax != 'Y' OR tax IS NULL )";
- #not covered in full by a monthly tax exemption (texas tax)
- "0 < ( $setup_taxable + $recur_taxable - $exempt )",
-
-} else {
-
- if ( $use_usage ) {
- $count_query = "SELECT COUNT(*), ";
- } else {
- $count_query = "SELECT COUNT(DISTINCT billpkgnum), ";
}
+ # recur/usage separation
+ $use_usage = $cgi->param('usage');
if ( $use_usage eq 'recurring' ) {
- $count_query .= "SUM(setup + recur - usage)";
- } elsif ( $use_usage eq 'usage' ) {
- $count_query .= "SUM(usage)";
- } elsif ( $unearned ) {
- $count_query .= "SUM(cust_bill_pkg.recur)";
- } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
- $count_query .= "SUM( COALESCE(cust_bill_pkg_tax_rate_location.amount, cust_bill_pkg.setup + cust_bill_pkg.recur))";
- } elsif ( $cgi->param('iscredit') eq 'rate') {
- $count_query .= "SUM( cust_credit_bill_pkg.amount )";
- } else {
- $count_query .= "SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)";
- }
- if ( $unearned ) {
+ my $recur_no_usage = FS::cust_bill_pkg->charged_sql('', '', no_usage => 1);
+ push @select, "($recur_no_usage) AS recur_no_usage";
+ $peritem[1] = 'recur_no_usage';
+ $total[1] = "SUM(cust_bill_pkg.setup + $recur_no_usage)";
+ $total_desc[1] .= ' (excluding usage)';
- #false laziness w/report_prepaid_income.cgi
+ } elsif ( $use_usage eq 'usage' ) {
- my $float = 'REAL'; #'DOUBLE PRECISION';
+ my $usage = FS::cust_bill_pkg->usage_sql();
+ push @select, "($usage) AS _usage";
+ # there's already a method named 'usage'
+ $peritem[1] = '_usage';
+ $peritem_desc[1] = 'Usage charge';
+ $total[1] = "SUM($usage)";
+ $total_desc[1] .= ' usage charges';
+ }
- my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)";
- my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $unearned
- THEN 0
- ELSE ($unearned - cust_bill_pkg.sdate)
- END)";
- #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)";
+} elsif ( $cgi->param('istax') ) {
- my $remaining = "(1 - $elapsed/$period)";
+ @peritem = ( 'setup' ); # taxes only have setup
+ @peritem_desc = ( 'Tax charge' );
- $count_query .= ", SUM($remaining * cust_bill_pkg.recur)";
+ push @where, 'cust_bill_pkg.pkgnum = 0';
- }
+ # tax location when using tax_rate_location
+ if ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) {
-}
+ $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
+ ' LEFT JOIN tax_rate_location USING ( taxratelocationnum )';
+ push @where, FS::tax_rate_location->location_sql(
+ map { $_ => (scalar($cgi->param($_)) || '') }
+ qw( district city county state locationtaxid )
+ );
-my $join_cust = ' JOIN cust_bill USING ( invnum )
- LEFT JOIN cust_main USING ( custnum ) ';
+ $total[1] = 'SUM(
+ COALESCE(cust_bill_pkg_tax_rate_location.amount,
+ cust_bill_pkg.setup + cust_bill_pkg.recur)
+ )';
+ } elsif ( $cgi->param('out') ) {
-my $join_pkg;
-if ( $cgi->param('nottax') ) {
+ $join_pkg = '
+ LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
+ ';
+ push @where, 'cust_bill_pkg_tax_location.billpkgnum IS NULL';
- $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
- 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');
+ # each billpkgnum should appear only once
+ $total[0] = 'COUNT(*)';
+ $total[1] = 'SUM(cust_bill_pkg.setup)';
-} elsif ( $cgi->param('istax') ) {
+ } else { # not locationtaxid or 'out'--the normal case
- #false laziness w/report_tax.cgi $taxfromwhere
- if ( scalar( grep( /locationtaxid/, $cgi->param ) ) ||
- $cgi->param('iscredit') eq 'rate') {
+ $join_pkg = '
+ LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
+ JOIN cust_main_county USING (taxnum)
+ ';
- $join_pkg .=
- ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '.
- ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) ';
+ # don't double-count the components of consolidated taxes
+ $total[0] = 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)';
+ $total[1] = 'SUM(cust_bill_pkg_tax_location.amount)';
+ }
- } elsif ( $conf->exists('tax-pkg_address') ) {
+ # taxclass
+ if ( $cgi->param('taxclassNULL') ) {
+ push @where, 'cust_main_county.taxclass IS NULL';
+ }
- $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
- LEFT JOIN cust_location USING ( locationnum ) ';
+ # taxname
+ if ( $cgi->param('taxnameNULL') ) {
+ push @where, 'cust_main_county.taxname IS NULL OR '.
+ 'cust_main_county.taxname = \'Tax\'';
+ } elsif ( $cgi->param('taxname') ) {
+ push @where, 'cust_main_county.taxname = '.
+ dbh->quote($cgi->param('taxname'));
+ }
- #quelle kludge, somewhat false laziness w/report_tax.cgi
- s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where;
+ # specific taxnums
+ if ( $cgi->param('taxnum') ) {
+ my $taxnum_in = join(',',
+ grep /^\d+$/, $cgi->param('taxnum')
+ );
+ push @where, "cust_main_county.taxnum IN ($taxnum_in)"
+ if $taxnum_in;
}
- if ( $cgi->param('iscredit') ) {
- $join_pkg .= ' JOIN cust_credit_bill_pkg USING ( billpkgnum';
- if ( $cgi->param('iscredit') eq 'rate' ) {
- $join_pkg .= ', billpkgtaxratelocationnum )';
- } elsif ( $conf->exists('tax-pkg_address') ) {
- $join_pkg .= ', billpkgtaxlocationnum )';
- push @where, "billpkgtaxratelocationnum IS NULL";
+ # report group (itemdesc)
+ if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
+ 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 {
- $join_pkg .= ' )';
- push @where, "billpkgtaxratelocationnum IS NULL";
+ die "guru meditation #00de: group_op $group_op\n";
}
}
-} else {
+ # itemdesc, for some reason
+ if ( $cgi->param('itemdesc') ) {
+ if ( $cgi->param('itemdesc') eq 'Tax' ) {
+ push @where, "(itemdesc='Tax' OR itemdesc is null)";
+ } else {
+ push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc'));
+ }
+ }
- #die?
- warn "neiether nottax nor istax parameters specified";
- #same as before?
- $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart ) ';
+} # nottax / istax
-}
+# credit
+if ( $cgi->param('credit') ) {
-my $where = ' WHERE '. join(' AND ', @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";
-}
+ my $credit_sub;
+
+ if ( $cgi->param('istax') ) {
+ # then we need to group/join by billpkgtaxlocationnum, to get only the
+ # relevant part of partial taxes
+ my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
+ reason.reason as reason_text, access_user.username AS username_text,
+ billpkgtaxlocationnum, billpkgnum
+ FROM cust_credit_bill_pkg
+ JOIN cust_credit_bill USING (creditbillnum)
+ JOIN cust_credit USING (crednum)
+ LEFT JOIN reason USING (reasonnum)
+ LEFT JOIN access_user USING (usernum)
+ GROUP BY billpkgnum, billpkgtaxlocationnum, reason.reason,
+ access_user.username";
+
+ if ( $cgi->param('out') ) {
+
+ # find credits that are applied to the line items, but not to
+ # a cust_bill_pkg_tax_location link
+ $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit
+ USING (billpkgnum)";
+ push @where, 'item_credit.billpkgtaxlocationnum IS NULL';
+
+ } else {
+
+ # find credits that are applied to the CBPTL links that are
+ # considered "interesting" by the report criteria
+ $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit
+ USING (billpkgtaxlocationnum)";
-my @select = ( 'cust_bill_pkg.*',
- 'cust_bill._date', );
+ }
-push @select, 'part_pkg.pkg',
- 'part_pkg.freq',
- unless $cgi->param('istax');
+ } else {
+ # then only group by billpkgnum
+ my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount,
+ reason.reason as reason_text, access_user.username AS username_text,
+ billpkgnum
+ FROM cust_credit_bill_pkg
+ JOIN cust_credit_bill USING (creditbillnum)
+ JOIN cust_credit USING (crednum)
+ LEFT JOIN reason USING (reasonnum)
+ LEFT JOIN access_user USING (usernum)
+ GROUP BY billpkgnum, reason.reason, access_user.username";
+ $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)";
+ }
-push @select, 'cust_main.custnum',
- FS::UI::Web::cust_sql_fields();
+ push @where, 'item_credit.billpkgnum IS NOT NULL';
+ push @select, 'item_credit.credit_amount',
+ 'item_credit.username_text',
+ 'item_credit.reason_text';
+ push @peritem, 'credit_amount', 'username_text', 'reason_text';
+ push @peritem_desc, 'Credited', 'By', 'Reason';
+ push @total, 'SUM(credit_amount)';
+ push @total_desc, "$money_char%.2f credited";
+} # if credit
+
+push @select, 'cust_main.custnum', FS::UI::Web::cust_sql_fields();
+
+my $where = join(' AND ', @where);
+$where &&= "WHERE $where";
my $query = {
'table' => 'cust_bill_pkg',
'addl_from' => "$join_cust $join_pkg",
'hashref' => {},
- 'select' => join(', ', @select ),
+ 'select' => join(",\n", @select ),
'extra_sql' => $where,
- 'order_by' => 'ORDER BY _date, billpkgnum',
+ 'order_by' => 'ORDER BY cust_bill._date, cust_bill_pkg.billpkgnum',
};
-my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
-my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+my $count_query =
+ 'SELECT ' . join(',', @total) .
+ " FROM cust_bill_pkg $join_cust $join_pkg
+ $where";
-my $conf = new FS::Conf;
-my $money_char = $conf->config('money_char') || '$';
+shift @total_desc; #the first one is implicit
-my $owed_sub = sub {
- $money_char. shift->owed_recur; #_recur :/
-};
+@peritem_desc = map {emt($_)} @peritem_desc;
+my @peritem_sub = map {
+ my $field = $_;
+ if ($field =~ /_text$/) { # kludge for credit reason/username fields
+ sub {$_[0]->get($field)};
+ } else {
+ sub { sprintf($money_char.'%.2f', $_[0]->get($field)) }
+ }
+} @peritem;
+my @peritem_null = map { '' } @peritem; # placeholders
+my $peritem_align = 'r' x scalar(@peritem);
-my $payment_date_sub = sub {
- #my $cust_bill_pkg = shift;
- my @cust_pay = sort { $a->_date <=> $b->_date }
- map $_->cust_bill_pay->cust_pay,
- shift->cust_bill_pay_pkg('recur') #recur :/
- or return '';
- time2str('%b %d %Y', $cust_pay[-1]->_date );
-};
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+warn "\n\nQUERY:\n".Dumper($query)."\n\nCOUNT_QUERY:\n$count_query\n\n"
+ if $cgi->param('debug');
</%init>
diff --git a/httemplate/search/cust_bill_pkg_referral.html b/httemplate/search/cust_bill_pkg_referral.html
new file mode 100644
index 000000000..77b486021
--- /dev/null
+++ b/httemplate/search/cust_bill_pkg_referral.html
@@ -0,0 +1,304 @@
+<& elements/search.html,
+ 'title' => emt('Sales with advertising source'),
+ 'name' => emt('line items'),
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [
+ ($setup ? $money_char. '%.2f setup' : ()),
+ ($recur ? $money_char. '%.2f recurring' : ()),
+ ($usage ? $money_char. '%.2f usage' : ()),
+ ],
+ 'header' => [
+ emt('Description'),
+ ($setup ? emt('Setup') : ()),
+ ($recur ? emt('Recurring') : ()),
+ ($usage ? emt('Usage') : ()),
+ emt('Invoice'),
+ emt('Invoice date'),
+ emt('Paid'),
+ emt('Payment date'),
+ emt('Pkg. status'),
+ emt('Pkg. class'),
+ '', #report class
+ emt('Cust#'),
+ emt('Customer'),
+ emt('Ad source'),
+ emt('Agent'),
+ ],
+ 'fields' => [
+ 'pkg',
+ ($setup ? money_sub('setup') : ()),
+ ($recur ? money_sub('recur_no_usage') : ()),
+ ($usage ? money_sub('recur_usage') : ()),
+ 'invnum',
+ date_sub('_date'),
+ money_sub('paid'),
+ date_sub('last_pay'),
+ sub {
+ my $cust_pkg = shift->cust_pkg;
+ $cust_pkg ? ucfirst($cust_pkg->status) : '';
+ },
+ 'classname',
+ sub { # report_option
+ my $cust_bill_pkg = shift;
+ my $pkgpart = $cust_bill_pkg->pkgpart_override
+ || $cust_bill_pkg->cust_pkg->pkgpart;
+ if ( !exists($report_classes{$pkgpart}) ) {
+ my $part_pkg = FS::part_pkg->by_key($pkgpart);
+ my %opts = $part_pkg->options;
+ $report_classes{$pkgpart} = [
+ map { /^report_option_(\d+)/ ?
+ $report_option_name{$1} :
+ () }
+ keys %opts
+ ];
+ }
+ join( '<BR>', @{ $report_classes{$pkgpart} });
+ },
+ 'custnum',
+ 'name',
+ 'referral', # from query
+ 'agent',
+ ],
+ 'sort_fields' => [
+ '',
+ ($setup ? 'setup' : ()),
+ ($recur ? 'recur_no_usage' : ()),
+ ($usage ? 'recur_usage' : ()),
+ 'invnum',
+ '_date',
+ 'paid',
+ 'last_pay',
+ '', #package status
+ 'classname',
+ '', #report_option
+ 'custnum',
+ '',
+ 'referral',
+ 'agent',
+ ],
+ 'links' => [
+ '', #package/item desc
+ ('') x $x, #setup/recur/usage
+ $ilink, #invnum
+ $ilink, #invoice date
+ '', #paid amt
+ '', #payment date
+ '', #pkg status
+ '', #classnum
+ '', #report class
+ $clink, #custnum
+ $clink, #customer name
+ '', #referral
+ '', #agent
+ ],
+ #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
+ 'align' => 'l' . ('r' x $x) . 'rcrccccrlll',
+ 'color' => [ ('') x (5 + $x),
+ sub {
+ my $cust_pkg = shift->cust_pkg;
+ $cust_pkg ? ucfirst($cust_pkg->statuscolor) : '';
+ },
+ ('') x 6,
+ ],
+ 'style' => [
+ ('') x (5 + $x),
+ 'b',
+ ('') x 6
+ ],
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $setup = $cgi->param('setup') ? 1 : 0;
+my $recur = $cgi->param('recur') ? 1 : 0;
+my $usage = $cgi->param('usage') ? 1 : 0;
+
+my $x = $setup + $recur + $usage;
+
+my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
+my ($join_cust, $join_pkg ) = ('', '');
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+my @where = ( $agentnums_sql,
+ 'cust_bill_pkg.pkgnum != 0', # exclude taxes
+ "cust_bill._date >= $beginning",
+ "cust_bill._date <= $ending",
+ );
+
+my @status_where;
+foreach my $status ($cgi->param('status')) {
+ if ( $status =~ /^([- a-z]+)$/ ) { #"one-time charge"
+ push @status_where, "'$status'";
+ }
+}
+if ( @status_where ) {
+ push @where, '('. FS::cust_pkg->status_sql.
+ ') IN (' . join(',', @status_where) .')';
+}
+
+my @refnum;
+foreach my $refnum ($cgi->param('refnum')) {
+ if ( $refnum =~ /^\d+$/ ) {
+ push @refnum, $refnum;
+ }
+}
+if ( @refnum ) {
+ push @where, 'cust_main.refnum IN ('.join(',', @refnum).')';
+}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+#classnum
+# not specified: all classes
+# 0: empty class
+# N: classnum
+my $use_override = 1; #$cgi->param('use_override');
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ my $comparison = '';
+ if ( $1 == 0 ) {
+ $comparison = "IS NULL";
+ } else {
+ $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";
+ }
+}
+
+# report option
+my @report_option = grep /^\d+$/, ( $cgi->param('report_option') );
+if ( @report_option ) {
+ @report_option = map { "'report_option_$_'" } @report_option;
+ push @where, "EXISTS(
+ SELECT 1 FROM part_pkg_option WHERE optionname IN (".
+ join(',', @report_option).") AND (
+ part_pkg_option.pkgpart = cust_pkg.pkgpart AND pkgpart_override IS NULL
+ OR part_pkg_option.pkgpart = pkgpart_override
+ )
+ )";
+}
+
+my $setup_sql =
+ FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'setup');
+my $recur_sql =
+ FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'recur', no_usage => 1);
+my $usage_sql = FS::cust_bill_pkg->usage_sql;
+
+# exclude zero-amount items
+my @orwhere;
+push @orwhere, "(cust_bill_pkg.setup > 0)" if $setup;
+push @orwhere, "($recur_sql > 0)" if $recur;
+push @orwhere, "($usage_sql > 0)" if $usage;
+push @where, '('.join(' OR ', @orwhere).')' if @orwhere;
+
+$join_cust = ' JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum )
+ LEFT JOIN part_referral USING ( refnum )
+ LEFT JOIN agent ON cust_main.agentnum = agent.agentnum
+ ';
+
+$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override
+ ON pkgpart_override = override.pkgpart
+ LEFT JOIN pkg_class ON '; #...
+
+if ( $use_override ) {
+ # join to whichever pkgpart is appropriate
+ $join_pkg .= '
+ ( pkgpart_override IS NULL AND part_pkg.classnum = pkg_class.classnum )
+ OR ( pkgpart_override IS NOT NULL AND override.classnum = pkg_class.classnum )';
+} else {
+ $join_pkg .= 'part_pkg.classnum = pkg_class.classnum';
+}
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+# setup and recurring only
+my $count_query = "SELECT
+ COUNT(billpkgnum)".
+ ($setup ? ", SUM($setup_sql)" : '').
+ ($recur ? ", SUM($recur_sql)" : '').
+ ($usage ? ", SUM($usage_sql)" : '').
+ " FROM cust_bill_pkg
+ $join_cust
+ $join_pkg
+ $where
+ ";
+
+my $paid_sql = FS::cust_bill_pkg->paid_sql('', '');
+my $last_pay_sql = "SELECT MAX(_date)
+ FROM cust_bill_pay JOIN cust_bill_pay_pkg USING (billpaynum)
+ WHERE cust_bill_pay_pkg.billpkgnum = cust_bill_pkg.billpkgnum";
+
+push @select, 'part_pkg.pkg',
+ 'part_pkg.freq',
+ 'cust_main.custnum',
+ 'cust_main.first',
+ 'cust_main.last',
+ 'cust_main.company',
+ 'part_referral.referral',
+ "($paid_sql) AS paid",
+ "($last_pay_sql) AS last_pay",
+ "($recur_sql) AS recur_no_usage",
+ "($usage_sql) AS recur_usage",
+ 'pkg_class.classname',
+ 'agent.agent',
+ ;
+
+my $query = {
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => "$join_cust $join_pkg",
+ 'hashref' => {},
+ 'select' => join(",\n", @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY cust_bill._date, billpkgnum',
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my %report_classes; #cache
+my %report_option_name =
+ map { $_->num => $_->name } qsearch('part_pkg_report_option', {});
+
+# should this be in Mason.pm or something?
+sub money_sub {
+ $conf ||= new FS::Conf;
+ $money_char ||= $conf->config('money_char') || '$';
+ my $field = shift;
+ sub {
+ $money_char . sprintf('%.2f', $_[0]->get($field));
+ };
+}
+
+sub date_sub {
+ my $field = shift;
+ sub {
+ my $value = $_[0]->get($field);
+ $value ? time2str('%b %d %Y', $value) : '';
+ };
+}
+
+</%init>
diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html
index 917257034..38f03491d 100755
--- a/httemplate/search/cust_credit.html
+++ b/httemplate/search/cust_credit.html
@@ -96,6 +96,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$title = $agent->agent. " $title";
}
+if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @search, "refnum = $1";
+ my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } );
+ die "unknown refnum $1" unless $part_referral;
+ $title = $part_referral->referral. " $title";
+}
+
if ( $unapplied ) {
push @search, FS::cust_credit->unapplied_sql . ' > 0';
}
diff --git a/httemplate/search/cust_credit_bill.html b/httemplate/search/cust_credit_bill.html
index 7f9eb7887..9fd6a987a 100644
--- a/httemplate/search/cust_credit_bill.html
+++ b/httemplate/search/cust_credit_bill.html
@@ -85,6 +85,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$title = $agent->agent. " $title";
}
+if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @search, "refnum = $1";
+ my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } );
+ die "unknown refnum $1" unless $part_referral;
+ $title = $part_referral->referral. " $title";
+}
+
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
push @search, "cust_bill._date >= $beginning ",
"cust_bill._date <= $ending";
diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html
index fef295e62..4612118a2 100644
--- a/httemplate/search/cust_credit_bill_pkg.html
+++ b/httemplate/search/cust_credit_bill_pkg.html
@@ -167,7 +167,7 @@ if ( $cgi->param('taxclass')
}
-my @loc_param = qw( city county state country );
+my @loc_param = qw( district city county state country );
if ( $cgi->param('out') ) {
@@ -215,7 +215,7 @@ if ( $cgi->param('out') ) {
my %ph = ( 'county' => dbh->quote($_),
map { $_ => dbh->quote( $cgi->param($_) ) }
- qw( city state country )
+ qw( district city state country )
);
my ( $loc_sql, @param ) = FS::cust_pkg->location_sql;
diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html
index fd87aa575..361c8ad2f 100644
--- a/httemplate/search/cust_credit_refund.html
+++ b/httemplate/search/cust_credit_refund.html
@@ -78,6 +78,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$title = $agent->agent. " $title";
}
+if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @search, "refnum = $1";
+ my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } );
+ die "unknown refnum $1" unless $part_referral;
+ $title = $part_referral->referral. " $title";
+}
+
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
push @search, "cust_credit._date >= $beginning ",
"cust_credit._date <= $ending";
diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html
index e87b21474..08800d431 100644
--- a/httemplate/search/cust_main-zip.html
+++ b/httemplate/search/cust_main-zip.html
@@ -4,8 +4,8 @@
'query' => $sql_query,
'count_query' => $count_sql,
'header' => [ 'Zip code', 'Customers', ],
- #'fields' => [ 'zip', 'num_cust', ],
- 'links' => [ '', sub { 'somewhere'; } ],
+ 'fields' => [ 0, 1 ],
+ 'links' => [ '', $link ],
)
%>
<%init>
@@ -63,48 +63,36 @@ sub strip_plus4 {
END";
}
-my( $zip, $czip);
-if ( $cgi->param('column') eq 'ship_zip' ) {
-
- my $casewhen_noship =
- "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN ";
-
- $czip = "$casewhen_noship zip ELSE ship_zip END";
-
- if ( $cgi->param('ignore_plus4') ) {
- $zip = $casewhen_noship. strip_plus4('zip').
- " ELSE ". strip_plus4('ship_zip'). ' END';
-
- } else {
- $zip = $casewhen_noship. fieldorempty('zip').
- " ELSE ". fieldorempty('ship_zip'). ' END';
- }
+$cgi->param('column') =~ /^(bill|ship)$/;
+my $location = $1 || 'bill';
+$location .= '_locationnum';
+my $zip;
+if ( $cgi->param('ignore_plus4') ) {
+ $zip = strip_plus4('cust_location.zip');
} else {
-
- $czip = 'zip';
-
- if ( $cgi->param('ignore_plus4') ) {
- $zip = strip_plus4('zip');
- } else {
- $zip = fieldorempty('zip');
- }
-
+ $zip = fieldorempty('cust_location.zip');
}
# construct the queries and send 'em off
+my $join = "JOIN cust_location ON (cust_main.$location = cust_location.locationnum)";
+
my $sql_query =
"SELECT $zip AS zipcode,
COUNT(*) AS num_cust
FROM cust_main
+ $join
$where
GROUP BY zipcode
- ORDER BY num_cust DESC
+ ORDER BY num_cust DESC, $zip ASC
";
-my $count_sql = "select count(distinct $czip) from cust_main $where";
+my $count_sql =
+ "SELECT COUNT(DISTINCT cust_location.zip)
+ FROM cust_main $join $where";
-# XXX should link...
+my $link = [ $p.'search/cust_main.html?zip=',
+ sub { $_[0]->[0] } ];
</%init>
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
index 859ef04e6..7c3ad3384 100755
--- a/httemplate/search/cust_main.cgi
+++ b/httemplate/search/cust_main.cgi
@@ -81,13 +81,8 @@
<TR>
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH>
<TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('(bill) name') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('company') |h %></TH>
-
-%if ( defined dbdef->table('cust_main')->column('ship_last') ) {
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('(service) name') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('company') |h %></TH>
-%}
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Name') |h %></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Company') |h %></TH>
%foreach my $addl_header ( @addl_headers ) {
<TH CLASS="grid" BGCOLOR="#cccccc"><% $addl_header %></TH>
@@ -172,25 +167,6 @@
<% $pcompany %>
</TD>
-% if ( defined dbdef->table('cust_main')->column('ship_last') ) {
-% my($ship_last,$ship_first,$ship_company)=(
-% $cust_main->ship_last || $cust_main->getfield('last'),
-% $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first,
-% $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company,
-% );
-% my $pship_company = $ship_company
-% ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>!
-% : '<FONT SIZE=-1>&nbsp;</FONT>';
-%
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan %>>
- <A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A>
- </TD>
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan %>>
- <% $pship_company %></A>
- </TD>
-% }
-%
% foreach my $addl_col ( @addl_cols ) {
% if ( $addl_col eq 'tickets' ) {
% if ( @custom_priorities ) {
@@ -492,9 +468,10 @@ if ( $cgi->param('browse')
if ( $cgi->param('search_cust') ) {
$sortby = \*company_sort;
$orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )";
- push @cust_main, smart_search( 'search' => scalar($cgi->param('search_cust')),
- 'no_fuzzy_on_exact' => 1, #pref?
- );
+ push @cust_main, smart_search(
+ 'search' => scalar($cgi->param('search_cust')),
+ 'no_fuzzy_on_exact' => ! $curuser->option('enable_fuzzy_on_exact'),
+ );
}
@cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main
diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html
index 498024ba0..fa79b4dfb 100755
--- a/httemplate/search/cust_main.html
+++ b/httemplate/search/cust_main.html
@@ -33,9 +33,7 @@
<%init>
die "access denied"
- unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') &&
- $FS::CurrentUser::CurrentUser->access_right('List packages')
- );
+ unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search');
my %search_hash = ();
@@ -43,11 +41,10 @@ my %search_hash = ();
#scalars
my @scalars = qw (
- agentnum status address paydate_year paydate_month invoice_terms
+ agentnum status address zip paydate_year paydate_month invoice_terms
no_censustract with_geocode custbatch usernum
cancelled_pkgs
cust_fields flattened_pkgs
- refnum
);
for my $param ( @scalars ) {
@@ -56,7 +53,7 @@ for my $param ( @scalars ) {
}
#lists
-for my $param (qw( classnum payby tagnum )) {
+for my $param (qw( classnum refnum payby tagnum )) {
$search_hash{$param} = [ $cgi->param($param) ];
}
@@ -64,14 +61,22 @@ for my $param (qw( classnum payby tagnum )) {
# parse dates
###
-foreach my $field (qw( signupdate )) {
+foreach my $field (qw( signupdate birthdate spouse_birthdate anniversary_date )) {
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
- next if $beginning == 0 && $ending == 4294967295 && !defined($cgi->param('signuphour'));
+ next if $beginning == 0 && $ending == 4294967295 && ( $field ne 'signupdate' || !defined($cgi->param('signuphour')) );
#or $disable{$cgi->param('status')}->{$field};
- $search_hash{$field} = [ $beginning, $ending, $cgi->param('signuphour') ];
+ unless ( $field eq 'signupdate' ) {
+ $beginning -= 43200;
+ $ending -= 43200;
+ }
+
+ my @ary = ( $beginning, $ending );
+ push @ary, scalar($cgi->param('signuphour')) if $field eq 'signupdate';
+
+ $search_hash{$field} = \@ary;
}
diff --git a/httemplate/search/cust_pay_pending.html b/httemplate/search/cust_pay_pending.html
index 8b7350853..2afce0ce9 100755
--- a/httemplate/search/cust_pay_pending.html
+++ b/httemplate/search/cust_pay_pending.html
@@ -5,7 +5,6 @@
'name_verb' => 'pending',
'disable_link' => 1,
'disable_by' => 1, #add otaker to cust_pay_pending?
- 'html_init' => include('/elements/init_overlib.html'),
'addl_header' => [ 'Time', 'Payment Status', ],
'addl_fields' => [ sub { time2str('%r', shift->_date ) },
$status_sub,
diff --git a/httemplate/search/cust_pkg_summary.html b/httemplate/search/cust_pkg_summary.html
index f9adf044e..8c05f7382 100644
--- a/httemplate/search/cust_pkg_summary.html
+++ b/httemplate/search/cust_pkg_summary.html
@@ -23,6 +23,6 @@
<%init>
die "access denied"
- unless $curuser->access_right('Summarize packages');
+ unless $FS::CurrentUser::CurrentUser->access_right('Summarize packages');
</%init>
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
index 9cf4bbda6..2adcbd76f 100644
--- a/httemplate/search/cust_svc.html
+++ b/httemplate/search/cust_svc.html
@@ -13,7 +13,10 @@
sub {
#$_[0]->svc. ': '. $_[0]->label;
my($label, $value, $svcdb) = $_[0]->label;
- "$label: $value";
+ my $id = $_[0]->agent_svcid
+ ? $_[0]->agent_svcid.': '
+ : '';
+ "$label: $id$value";
},
# package?
\&FS::UI::Web::cust_fields,
@@ -42,79 +45,75 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('List services');
-my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '.
- ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN cust_main USING ( custnum ) ';
+my $sql_query;
-my @extra_sql = ();
my $orderby = 'ORDER BY svcnum'; #has to be ordered by something
#for pagination to work
+
if ( length( $cgi->param('search_svc') ) ) {
- my $string = $cgi->param('search_svc');
- $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace
-
- # implement fuzzy searching in subclasses too at some point?
- # service searching maybe shouldn't be fuzzy...
-
- push @extra_sql,
- ' ( '. join(' OR ',
- map { my $table = $_;
- my $search_sql = "FS::$table"->search_sql($string);
- " ( svcdb = '$table'
- AND 0 < ( SELECT COUNT(*) FROM $table
- WHERE $table.svcnum = cust_svc.svcnum
- AND $search_sql
- )
- ) ";
- }
- FS::part_svc->svc_tables
- ). ' ) ';
-
-} elsif ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
-
- $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb";
- push @extra_sql, "svcdb = '$1'";
- $addl_from .= " LEFT JOIN $1 USING ( svcnum ) ";
-
- push @extra_sql, 'pkgnum IS NULL'
- if $cgi->param('magic') eq 'unlinked';
-
- if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
- my $sortby = $1;
- $orderby = "ORDER BY $sortby";
+ $sql_query = {
+ FS::cust_svc->smart_search_param(
+ 'search' => scalar($cgi->param('search_svc'))
+ )
+ };
+
+} else {
+
+ my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+ my @extra_sql = ();
+
+ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+ $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb";
+ push @extra_sql, "svcdb = '$1'";
+ $addl_from .= " LEFT JOIN $1 USING ( svcnum ) ";
+
+ 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";
+
+ } else {
+ errorpage("No search term specified");
}
-} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ #here is the agent virtualization
+ push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ );
- push @extra_sql, "svcpart = $1";
+ my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
+
+ $sql_query = {
+ 'table' => 'cust_svc',
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ };
-} else {
- errorpage("No search term specified");
}
-#here is the agent virtualization
-push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql(
- 'null_right' => 'View/link unlinked services'
- );
-
-my $extra_sql = ' WHERE '. join(' AND ', @extra_sql );
-
-my $sql_query = {
- 'select' => join(', ',
- 'cust_svc.*',
- 'part_svc.*',
- 'cust_main.custnum',
- FS::UI::Web::cust_sql_fields(),
- ),
- 'table' => 'cust_svc',
- 'addl_from' => $addl_from,
- 'hashref' => {},
- 'extra_sql' => $extra_sql,
- 'order_by' => $orderby,
-};
+$sql_query->{'select'} = join(', ',
+ 'cust_svc.*',
+ 'part_svc.*',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields(),
+ );
+$sql_query->{'order_by'} = $orderby;
-my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql";
+my $count_query = "SELECT COUNT(*) FROM cust_svc ". $sql_query->{addl_from}.
+ ' '. $sql_query->{extra_sql};
my $link = sub {
my $cust_svc = shift;
diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi
index 3a5155ae8..1b767f846 100644
--- a/httemplate/search/cust_tax_exempt_pkg.cgi
+++ b/httemplate/search/cust_tax_exempt_pkg.cgi
@@ -103,7 +103,7 @@ my $join = "
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
-my @where = ();
+my @where = ("exempt_monthly = 'Y'");
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
if ( $beginning || $ending ) {
@@ -121,6 +121,7 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
}
if ( $cgi->param('out') ) {
+ # wtf? how would you ever get exemptions on a non-taxable package location?
push @where, "
0 = (
@@ -151,6 +152,11 @@ if ( $cgi->param('out') ) {
push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') )
if $cgi->param('taxclass');
+} elsif ( $cgi->param('taxnum') ) {
+
+ my $taxnum_in = join(',', grep /^\d+$/, $cgi->param('taxnum') );
+ push @where, "taxnum IN ($taxnum_in)" if $taxnum_in;
+
}
my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : '';
diff --git a/httemplate/search/customer_accounting_summary.html b/httemplate/search/customer_accounting_summary.html
index 8da8914c8..72a00ed95 100644
--- a/httemplate/search/customer_accounting_summary.html
+++ b/httemplate/search/customer_accounting_summary.html
@@ -30,6 +30,19 @@ elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
die "agentnum $agentnum not found!" unless $sel_agent;
}
my $title = $sel_agent ? $sel_agent->agent.' ' : '';
+
+my ($refnum,$sel_part_referral);
+#if ( $cgi->param('refnum') eq 'all' ) {
+# $refnum = 0;
+#} els
+if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ $refnum = $1;
+ $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } );
+ die "refnum $refnum not found!" unless $sel_part_referral;
+}
+$title .= $sel_part_referral->referral.' '
+ if $sel_part_referral;
+
$title .= 'Customer Accounting Summary Report';
my @custs = ();
@@ -45,6 +58,7 @@ die "invalid status" unless $status =~ /^\w+|$/;
foreach my $cust_main ( @custs ) {
next unless ($status eq '' || $status eq $cust_main->status);
next unless ($agentnum == 0 || $cust_main->agentnum eq $agentnum);
+ next unless ($refnum == 0 || $cust_main->refnum eq $refnum);
push @items, 'netsales', 'cashflow';
diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html
index cc1601455..eb7566494 100644
--- a/httemplate/search/elements/cust_main_dayranges.html
+++ b/httemplate/search/elements/cust_main_dayranges.html
@@ -108,13 +108,14 @@ my $ranges = $opt{'ranges'} ? delete($opt{'ranges'}) : [
my $range_sub = delete($opt{'range_sub'}); #or die
-my $offset = 0;
+my $as_of;
if($cgi->param('as_of')) {
- $offset = int((time - parse_datetime($cgi->param('as_of'))) / 86400);
- $opt{'title'} .= ' ('.$cgi->param('as_of').')' if $offset > 0;
+ $as_of = parse_datetime($cgi->param('as_of')) || '';
+ $opt{'title'} .= ' ('.$cgi->param('as_of').')' if $as_of;
}
-my $range_cols = join(',', map call_range_sub($range_sub, @$_, 'offset' => $offset ), @$ranges );
+my $range_cols = join(',',
+ map call_range_sub($range_sub, @$_, 'as_of' => $as_of ), @$ranges );
my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql;
@@ -144,7 +145,7 @@ unless ( $cgi->param('all_customers') ) {
my $negative = $cgi->param('negative') || 0;
push @where,
- call_range_sub($range_sub, $days, 0, 'offset' => $offset, 'no_as'=>1).
+ call_range_sub($range_sub, $days, 0, 'as_of' => $as_of, 'no_as'=>1).
($negative ? ' != 0' : ' > 0');
}
@@ -186,7 +187,9 @@ my $sql_query = {
my $total_sql =
"SELECT ".
- join(',', map call_range_sub( $range_sub, @$_, 'offset' => $offset, 'sum'=>1 ), @$ranges).
+ join(',',
+ map call_range_sub( $range_sub, @$_, 'as_of' => $as_of, 'sum'=>1 ),
+ @$ranges).
" FROM cust_main $where";
my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
@@ -251,10 +254,11 @@ sub call_range_sub {
my $as = $opt{'no_as'} ? '' : " AS rangecol_${startdays}_$enddays";
- my $offset = $opt{'offset'} || 0;
- # Always use $offset - 1day + 1sec = the last second of that day
- my $cutoff = DateTime->now->set(hour => 23, minute => 59, second => 59);
- $cutoff->subtract(days => $offset);
+ my $as_of = $opt{'as_of'} || time;
+ my $cutoff = DateTime->from_epoch(epoch => $as_of, time_zone => 'local');
+ $cutoff->truncate(to => 'day'); # local midnight on the report day
+ $cutoff->add(days => 1); # the day after that
+ $cutoff->subtract(seconds => 1); # the last second of the report day
my $start = $cutoff->clone;
$start->subtract(days => $startdays);
@@ -262,7 +266,7 @@ sub call_range_sub {
my $end = $cutoff->clone;
$end->subtract(days => $enddays);
- #warn "offset $offset (".$cutoff->epoch."), range $startdays-$enddays (".$start->epoch . '-' . ($enddays ? $end->epoch : '').")\n";
+ #warn "cutoff ".$cutoff->epoch.", range $startdays-$enddays (".$start->epoch . '-' . ($enddays ? $end->epoch : '').")\n";
my $sql = &{$range_sub}( $start->epoch,
$enddays ? $end->epoch : '',
$cutoff->epoch ); #%opt?
diff --git a/httemplate/search/elements/cust_pay_batch_top.html b/httemplate/search/elements/cust_pay_batch_top.html
index ce0ee9ed4..1dcc37ac1 100644
--- a/httemplate/search/elements/cust_pay_batch_top.html
+++ b/httemplate/search/elements/cust_pay_batch_top.html
@@ -14,7 +14,8 @@ Download batch in format <SELECT NAME="format">
% foreach ( keys %download_formats ) {
<OPTION VALUE="<%$_%>"><% $download_formats{$_} %></OPTION>
% }
-</SELECT>
+</SELECT>
+<& .select_gateway &>
% }
<INPUT TYPE="submit" VALUE="Download"></FORM><BR><BR></TR>
% } # end of download
@@ -31,7 +32,8 @@ Download batch in format <SELECT NAME="format">
'name' => 'FileUpload',
'action' => "${p}misc/upload-batch.cgi",
'num_files' => 1,
- 'fields' => [ 'batchnum', 'format' ],
+ 'fields' => [ 'batchnum', 'format', 'gatewaynum' ],
+ 'url' => $cgi->self_url,
'message' => 'Batch results uploaded.',
) %>
Upload results<BR></TR>
@@ -45,20 +47,22 @@ Upload results<BR></TR>
<BR></TR>
% if ( $fixed ) {
% if ( $fixed eq 'td_eft1464' ) { # special case
-<TR>Format <SELECT NAME="format">
+<TR>Upload in format <SELECT NAME="format">
<OPTION VALUE="td_eftack264">TD EFT Acknowledgement</OPTION>
<OPTION VALUE="td_eftret80">TD EFT Returned Items</OPTION>
-</SELECT></TR>
+</SELECT> </TR>
% }
% else {
<INPUT TYPE="hidden" NAME="format" VALUE="<% $fixed %>">
% }
% }
% else {
-<TR>Format <SELECT NAME="format">
+<TR>Upload in format <SELECT NAME="format">
% foreach ( keys(%upload_formats) ) {
<OPTION VALUE="<%$_%>"><% $upload_formats{$_} %></OPTION>
% }
+</SELECT>
+<& .select_gateway &>
% } # if $fixed
<TR><INPUT TYPE="submit" VALUE="Upload"></TR>
</FORM><BR>
@@ -82,6 +86,26 @@ Batch is <% $statustext{$status} %><BR>
<%$count%> payments batched<BR>
<%$money_char%><%$total%> total in batch<BR>
+<%def .select_gateway>
+% if ( $show_gateways ) {
+ or for gateway
+<& /elements/select-table.html,
+ empty_label => ' ',
+ field => 'gatewaynum',
+ table => 'payment_gateway',
+ name_col => 'label',
+ value_col => 'gatewaynum',
+ order_by => 'ORDER BY gatewaynum',
+ hashref => {
+ 'gateway_namespace' => 'Business::BatchPayment',
+ 'disabled' => '',
+ }
+&>
+% }
+</%def>
+<%shared>
+my $show_gateways = FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL");
+</%shared>
<%init>
my %opt = @_;
my $pay_batch = $opt{'pay_batch'} or return;
@@ -91,7 +115,7 @@ my $payby = $pay_batch->payby;
my $status = $pay_batch->status;
my $curuser = $FS::CurrentUser::CurrentUser;
my $batchnum = $pay_batch->batchnum;
-
+
my $fixed = $conf->config("batch-fixed_format-$payby");
tie my %download_formats, 'Tie::IxHash', (
diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html
index 002b1a4c2..c60411107 100755
--- a/httemplate/search/elements/cust_pay_or_refund.html
+++ b/httemplate/search/elements/cust_pay_or_refund.html
@@ -232,6 +232,13 @@ if ( $cgi->param('magic') ) {
$title = $agent->agent. " $title";
}
+ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) {
+ push @search, "refnum = $1";
+ my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } );
+ die "unknown refnum $1" unless $part_referral;
+ $title = $part_referral->referral. " $title";
+ }
+
if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
push @search, "custnum = $1";
}
@@ -350,6 +357,15 @@ if ( $cgi->param('magic') ) {
$orderby = "LOWER(company || ' ' || last || ' ' || first )";
+ } elsif ( $cgi->param('magic') eq 'batchnum' ) {
+
+ $cgi->param('batchnum') =~ /^(\d+)$/
+ or die "illegal batchnum: ".$cgi->param('batchnum');
+
+ push @search, "batchnum = $1";
+
+ $orderby = "LOWER(company || ' ' || last || ' ' || first )";
+
} else {
die "unknown search magic: ". $cgi->param('magic');
}
diff --git a/httemplate/search/elements/search-csv.html b/httemplate/search/elements/search-csv.html
index 9eb1b66d1..90230e6dc 100644
--- a/httemplate/search/elements/search-csv.html
+++ b/httemplate/search/elements/search-csv.html
@@ -27,10 +27,21 @@
% $csv->combine(@$row); #or die $csv->status;
% }
%
-%
<% $csv->string %>\
%
% }
+%
+% if ( $opt{'footer'} and !$opt{'no_csv_header'} ) {
+% my @footer;
+% foreach my $item (@{ $opt{'footer'} }) {
+% if ( ref($item) eq 'CODE' ) {
+% $item = &{$item}();
+% }
+% push @footer, $item;
+% }
+% $csv->combine(@footer);
+<% $csv->string %>\
+% }
<%init>
my %args = @_;
diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html
index 53167c26e..d7e81282b 100644
--- a/httemplate/search/elements/search-html.html
+++ b/httemplate/search/elements/search-html.html
@@ -134,9 +134,9 @@
% and !$opt{'disable_download'}
% and $type ne 'html-print' ) {
- <TD ALIGN="right">
+ <TD ALIGN="right" CLASS="noprint">
- Download full results<BR>
+ <% $opt{'download_label'} || 'Download full results' %><BR>
% $cgi->param('_type', "$xlsname.xls" );
as <A HREF="<% "$self_url?". $cgi->query_string %>">Excel spreadsheet</A><BR>
@@ -337,6 +337,11 @@
% map {
% if ( ref($_) eq 'CODE' ) {
% &{$_}($row);
+% } elsif ( ref($row) eq 'ARRAY' and
+% $_ =~ /^\d+$/ ) {
+% # for the 'straight SQL' case: specify fields
+% # by position
+% $row->[$_];
% } else {
% $row->$_();
% }
@@ -345,7 +350,8 @@
%
% ) {
%
-% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
+%# my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid';
+% my $class = 'grid';
%
% my $align = $aligns ? shift @$aligns : '';
% $align = " ALIGN=$align" if $align;
diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html
index 0b5636c0e..94d88b096 100644
--- a/httemplate/search/elements/search-xls.html
+++ b/httemplate/search/elements/search-xls.html
@@ -7,14 +7,20 @@ my $header = $args{'header'};
my $rows = $args{'rows'};
my %opt = %{ $args{'opt'} };
+my $override = scalar(@$rows) >= 65536 ? 'XLSX' : '';
+
+my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override);
+
+my $filename = $opt{'name'} || PL($opt{'name_singular'});
+$filename .= $format->{extension};
+
#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_header('Content-Disposition' =>
- 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xls"');
+http_header('Content-Type' => $format->{mime_type} );
+http_header('Content-Disposition' => qq!attachment;filename="$filename"! );
#http://support.microsoft.com/kb/812935
#http://support.microsoft.com/kb/323308
@@ -22,8 +28,8 @@ $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 $workbook = $format->{class}->new($XLS)
+ or die "Error opening Excel file: $!";
my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31));
@@ -42,14 +48,22 @@ my $default_format = $workbook->add_format(locked => 0);
my %money_format;
my $money_char = FS::Conf->new->config('money_char') || '$';
+my %date_format;
+xl_parse_date_init();
+
my $writer = sub {
# Wrapper for $worksheet->write.
# Do any massaging of the value/format here.
my ($r, $c, $value, $format) = @_;
- if ( $value =~ /^\Q$money_char\E(\d+\.?\d*)$/ ) {
+ # convert HTML entities
+ # both Spreadsheet::WriteExcel and Excel::Writer::XLSX accept UTF-8 strings
+ $value = decode_entities($value);
+
+ if ( $value =~ /^\Q$money_char\E(-?\d+\.?\d*)$/ ) {
# Currency: strip the symbol, clone the requested format,
# and format it for currency
$value = $1;
+# warn "formatting $value as money\n";
if ( !exists($money_format{$format}) ) {
$money_format{$format} = $workbook->add_format();
$money_format{$format}->copy($format);
@@ -57,6 +71,22 @@ my $writer = sub {
}
$format = $money_format{$format};
}
+ elsif ( $value =~ /^([A-Z][a-z]{2}) (\d{2}) (\d{4})$/ ) {
+ # Date: convert the value to an Excel date number and set
+ # the format
+ $value = xl_parse_date($value);
+# warn "formatting $value as date\n";
+ if ( !exists($date_format{$format}) ) {
+ $date_format{$format} = $workbook->add_format();
+ $date_format{$format}->copy($format);
+ $date_format{$format}->set_num_format('mmm dd yyyy');
+ }
+ $format = $date_format{$format};
+ }
+ else {
+ # String: replace line breaks with newlines
+ $value =~ s/<BR>/\n/gi;
+ }
$worksheet->write($r, $c, $value, $format);
};
@@ -104,6 +134,17 @@ foreach my $row ( @$rows ) {
}
+if ( $opt{'footer'} ) {
+ $r++;
+ $c = 0;
+ foreach my $item (@{ $opt{'footer'} }) {
+ if ( ref($item) eq 'CODE' ) {
+ $item = &{$item}();
+ }
+ $writer->( $r, $c++, $item, $header_format );
+ }
+}
+
$workbook->close();# or die "Error creating .xls file: $!";
http_header('Content-Length' => length($data) );
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
index 9bc66b6fa..eca68a2f8 100644
--- a/httemplate/search/elements/search.html
+++ b/httemplate/search/elements/search.html
@@ -162,7 +162,11 @@ Example:
# 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' => => [],
-
+
+
+ # miscellany
+ 'download_label' => 'Download this report',
+ # defaults to 'Download full results'
&>
</%doc>
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
index b2a15ef3d..aeaa012f4 100755
--- a/httemplate/search/pay_batch.cgi
+++ b/httemplate/search/pay_batch.cgi
@@ -14,11 +14,13 @@
'Type',
'First Download',
'Last Upload',
- 'Item Count',
- 'Amount',
+ '', # requests
+ '', # req amt
+ '', # payments
+ '', # pay amt
'Status',
],
- 'align' => 'rcllrrc',
+ 'align' => 'rcllrrrrc',
'fields' => [ 'batchnum',
sub {
FS::payby->shortname(shift->payby);
@@ -46,30 +48,44 @@
}
},
sub {
- my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+ my $c = FS::cust_pay_batch->count('batchnum = '.$_[0]->batchnum);
+ $c ? "$c requested" : ''
+ },
+ sub {
+ my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
my $sth = dbh->prepare($st)
or die dbh->errstr. "doing $st";
$sth->execute
- or die "Error executing \"$st\": ". $sth->errstr;
- $sth->fetchrow_arrayref->[0];
- },
- sub {
- my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+ or die "Error executing \"$st\": ". $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+ $total ? $money_char.sprintf('%.2f',$total) : '';
+ },
+ sub {
+ my $c = FS::cust_pay->count('batchnum = '.$_[0]->batchnum);
+ $c ? "$c paid" : ''
+ },
+ sub {
+ my $st = "SELECT SUM(paid) from cust_pay WHERE batchnum=" . shift->batchnum;
my $sth = dbh->prepare($st)
- or die dbh->errstr. "doing $st";
+ or die dbh->errstr. "doing $st";
$sth->execute
- or die "Error executing \"$st\": ". $sth->errstr;
- $sth->fetchrow_arrayref->[0];
- },
+ or die "Error executing \"$st\": ". $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
+ $total ? $money_char.sprintf('%.2f',$total) : '';
+ },
sub {
$statusmap{shift->status};
},
],
'links' => [
- $link,
+ '',
'',
- sub { shift->status eq 'O' ? $link : '' },
- sub { shift->status eq 'I' ? $link : '' },
+ sub { shift->status eq 'O' ? $cpb_link : '' },
+ sub { shift->status eq 'I' ? $cpb_link : '' },
+ $cpb_link,
+ $cpb_link,
+ $pay_link,
+ $pay_link,
],
'size' => [
'',
@@ -84,9 +100,42 @@
sub { shift->status eq 'I' ? "b" : '' },
],
'html_init' => $html_init,
+ 'html_foot' => include('.upload_incoming'),
)
-
%>
+<%def .upload_incoming>
+% if ( FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL") > 0 ) {
+<& /elements/form-file_upload.html,
+ name => 'FileUpload',
+ action => $p.'misc/upload-batch.cgi',
+ num_files => 1,
+ fields => [ 'gatewaynum' ],
+ message => 'Incoming batch uploaded.',
+&>
+<BR>
+<BR>
+Upload incoming batch from gateway
+<& /elements/select-table.html,
+ table => 'payment_gateway',
+ field => 'gatewaynum',
+ name_col => 'label',
+ value_col => 'gatewaynum',
+ order_by => 'ORDER BY gatewaynum',
+ empty_label => ' ',
+ hashref =>
+ { 'gateway_namespace' => 'Business::BatchPayment',
+ 'disabled' => '' },
+&>
+<BR>
+<& '/elements/file-upload.html',
+ field => 'file',
+ label => 'Filename',
+ no_table => 1,
+&>
+<INPUT TYPE="submit" VALUE="Upload">
+</FORM>
+% }
+</%def>
<%init>
die "access denied"
@@ -130,11 +179,14 @@ push @where,
my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : '';
-my $link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ];
+my $cpb_link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ];
+my $pay_link = [ "${p}search/cust_pay.html?magic=batchnum;batchnum=", 'batchnum' ];
my $resolved = $cgi->param('resolved') || 0;
$cgi->param('resolved' => !$resolved);
my $html_init = '<A HREF="' . $cgi->self_url . '"><I>'.
($resolved ? 'Hide' : 'Show') . ' resolved batches</I></A><BR>';
+my $money_char = FS::Conf->new->config('money_char') || '$';
+
</%init>
diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/prepaid_income.html
index 2fe5b6f10..ebac5a2a9 100644
--- a/httemplate/search/report_prepaid_income.cgi
+++ b/httemplate/search/prepaid_income.html
@@ -56,9 +56,11 @@
<BR>
<% $actual_label %><% $actual_label ? 'u' : 'U' %>nearned revenue
-is the amount of unearned revenue
+is the as-yet-unearned portion of revenue
<% $actual_label ? 'Freeside has actually' : '' %>
-invoiced for packages with longer-than monthly terms.
+invoiced for packages with
+<% $cgi->param('include_monthly') ? 'terms extending into the future.'
+ : 'longer-than monthly terms.' %>
% if ( $legacy ) {
<BR><BR>
@@ -82,13 +84,25 @@ my $actual_label = $legacy ? 'Actual ' : '';
#doesn't yet deal with daily/weekly packages
+my $mode = $cgi->param('mode');
+
my $time = time;
my $now = $cgi->param('date') && parse_datetime($cgi->param('date')) || $time;
$now =~ /^(\d+)$/ or die "unparsable date?";
$now = $1;
-my $link = "cust_bill_pkg.cgi?nottax=1;unearned_now=$now";
+my $dt = DateTime->from_epoch(epoch => $now, time_zone => 'local');
+$dt->truncate(to => 'day'); # local midnight on the report day
+$dt->add(days => 1); # the day after that
+$dt->subtract(seconds => 1); # the last second of the report day
+$now = $dt->epoch;
+
+my $link = "unearned_detail.html?date=$now;mode=$mode";
+
+if ( $cgi->param('include_monthly') ) {
+ $link .= ';include_monthly=1';
+}
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -108,6 +122,13 @@ my @where = ();
#here is the agent virtualization
push @where, $curuser->agentnums_sql( 'table'=>'cust_main' );
+my $status = '';
+if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
+ $status = $1;
+ $link .= ";status=$status";
+ push @where, FS::cust_main->cust_status_sql . " = '$status'";
+}
+
my %total = ();
my %total_legacy = ();
foreach my $agentnum (@agentnums) {
@@ -117,66 +138,54 @@ foreach my $agentnum (@agentnums) {
my( $total, $total_legacy ) = ( 0, 0 );
- # my @cust_bill_pkg =
- # grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[hdw])$/ }
- # 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 },
- # 'sdate' => { op=>'<', value=>$now },
- # 'edate' => { op=>'>', value=>$now },
- # },
- # 'extra_sql' => $where,
- # });
- #
- # foreach my $cust_bill_pkg ( @cust_bill_pkg) {
- # my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
- #
- # my $elapsed = $now - $cust_bill_pkg->sdate;
- # $elapsed = 0 if $elapsed < 0;
- #
- # my $remaining = 1 - $elapsed/$period;
- #
- # my $unearned = $remaining * $cust_bill_pkg->recur;
- # $total += $unearned;
- #
- # }
-
- #re-written in sql:
-
- #false laziness w/cust_bill_pkg.cgi
-
- my $float = 'REAL'; #'DOUBLE PRECISION';
-
- my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)";
- my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $now
- THEN 0
- ELSE ($now - cust_bill_pkg.sdate)
- END)";
- #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)";
-
- my $remaining = "(1 - $elapsed/$period)";
-
- my $select = "SUM($remaining * cust_bill_pkg.recur)";
-
- #[...]
-
- my $sql = "SELECT $select FROM cust_bill_pkg
- LEFT JOIN cust_pkg USING ( pkgnum )
- LEFT JOIN part_pkg USING ( pkgpart )
- LEFT JOIN cust_main USING ( custnum )
- WHERE pkgpart > 0
- AND sdate < $now
- AND edate > $now
- AND cust_bill_pkg.recur != 0
- AND part_pkg.freq != '0'
+ my @opt = ($now, '', setuprecur => 'recur', no_usage => 1);
+ # balance owed, recurring only, not including usage charges
+ my $unearned_base;
+ if ( $mode eq 'billed' ) {
+ $unearned_base = '( ' .
+ FS::cust_bill_pkg->charged_sql(@opt) . ' - ' .
+ FS::cust_bill_pkg->credited_sql(@opt) . ' )';
+ } elsif ( $mode eq 'paid' ) {
+ $unearned_base = FS::cust_bill_pkg->paid_sql(@opt);
+ }
+
+ my $edate_zero = midnight_sql('edate');
+ my $sdate_zero = midnight_sql('sdate');
+ my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )";
+ my $remaining = "GREATEST(
+ CAST( ($edate_zero - $now) / 86400.0 AS DECIMAL(10,0) ),
+ 0)";
+ my $fraction = "$remaining / $period";
+
+ my $unearned_sql = "CAST(
+ GREATEST( $unearned_base * $fraction, 0 )
+ AS DECIMAL(10,2)
+ )";
+
+ my $select = "SUM( $unearned_sql )";
+
+ if ( !$cgi->param('include_monthly') ) {
+ # all except freq != 0; one-time charges should never be included
+ $where .= "
AND part_pkg.freq != '1'
AND part_pkg.freq NOT LIKE '%h'
AND part_pkg.freq NOT LIKE '%d'
- AND part_pkg.freq NOT LIKE '%w'
+ AND part_pkg.freq NOT LIKE '%w'";
+ }
+
+ # $mode actually doesn't matter here, since unpaid invoices have zero
+ # unearned revenue
+
+ my $sql =
+ "SELECT $select FROM cust_bill_pkg
+ LEFT JOIN cust_pkg ON (cust_bill_pkg.pkgnum = cust_pkg.pkgnum)
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)
+ WHERE pkgpart > 0
+ AND cust_bill_pkg.sdate < $now
+ AND cust_bill_pkg.edate > $now
+ AND cust_bill_pkg.recur != 0
+ AND part_pkg.freq != '0'
$where
";
diff --git a/httemplate/search/quotation.html b/httemplate/search/quotation.html
new file mode 100755
index 000000000..259c85c22
--- /dev/null
+++ b/httemplate/search/quotation.html
@@ -0,0 +1,268 @@
+<& elements/search.html,
+ 'title' => emt('Quotation Search Results'),
+ 'html_init' => $html_init,
+ 'menubar' => $menubar,
+ 'name' => 'quotations',
+ 'query' => $sql_query,
+ 'count_query' => $count_query,
+ 'count_addl' => $count_addl,
+ 'redirect' => $link,
+ 'header' => [ emt('Quotation #'),
+ emt('Setup'),
+ emt('Recurring'),
+ emt('Date'),
+ emt('Prospect'),
+ emt('Customer'),
+ ],
+ 'fields' => [
+ 'quotationnum',
+ sub { $money_char. shift->total_setup },
+ sub { $money_char. shift->total_recur },
+ sub { time2str('%b %d %Y', shift->_date ) },
+ sub { my $prospect_main = shift->prospect_main;
+ $prospect_main ? $prospect_main->name : '';
+ },
+ sub { my $cust_main = shift->cust_main;
+ $cust_main ? $cust_main->name : '';
+ },
+ #\&FS::UI::Web::cust_fields,
+ ],
+ 'sort_fields' => [
+ 'quotationnum',
+ '', #FS::quotation->total_setup_sql,
+ '', #FS::quotation->total_recur_sql,
+ '_date',
+ '',
+ '',
+ ],
+ 'align' => 'rrrrll', #.FS::UI::Web::cust_aligns(),
+ 'links' => [
+ $link,
+ $link,
+ $link,
+ $link,
+ $prospect_link,
+ $cust_link,
+ #( map { $_ ne 'Cust. Status' ? $clink : '' }
+ # FS::UI::Web::cust_header()
+ #),
+ ],
+# 'color' => [
+# '',
+# '',
+# '',
+# '',
+# '',
+# FS::UI::Web::cust_colors(),
+# ],
+# 'style' => [
+# '',
+# '',
+# '',
+# '',
+# '',
+# FS::UI::Web::cust_styles(),
+# ],
+&>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('List quotations');
+
+my $join_prospect_main = 'LEFT JOIN prospect_main USING ( prospectnum )';
+my $join_cust_main = 'LEFT JOIN cust_main ON ( quotation.custnum = cust_main.custnum )';
+
+#here is the agent virtualization
+my $agentnums_sql = ' ( '. $curuser->agentnums_sql( table=>'prospect_main' ).
+ ' OR '. $curuser->agentnums_sql( table=>'cust_main' ).
+ ' ) ';
+
+my( $count_query, $sql_query );
+my $count_addl = '';
+my %search;
+
+#if ( $cgi->param('quotationnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) {
+#
+# my $where = "WHERE quotationnum = $2 AND $agentnums_sql";
+#
+# $count_query = "SELECT COUNT(*) FROM quotation $join_prospect_main $join_cust_main $where";
+#
+# $sql_query = {
+# 'table' => 'quotation',
+# 'addl_from' => "$join_prospect_main $join_cust_main",
+# 'hashref' => {},
+# 'extra_sql' => $where,
+# };
+#
+#} else {
+
+ #some false laziness w/cust_bill::re_X
+ my $orderby = 'ORDER BY quotation._date';
+
+ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $search{'agentnum'} = $1;
+ }
+
+# if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
+# $search{'refnum'} = $1;
+# }
+
+ if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ $search{'prospectnum'} = $1;
+ }
+
+ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $search{'custnum'} = $1;
+ }
+
+ # begin/end/beginning/ending
+ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, '');
+ $search{'_date'} = [ $beginning, $ending ]
+ unless $beginning == 0 && $ending == 4294967295;
+
+ if ( $cgi->param('quotationnum_min') =~ /^\s*(\d+)\s*$/ ) {
+ $search{'quotationnum_min'} = $1;
+ }
+ if ( $cgi->param('quotationnum_max') =~ /^\s*(\d+)\s*$/ ) {
+ $search{'quotationnum_max'} = $1;
+ }
+
+ #amounts
+ $search{$_} = [ FS::UI::Web::parse_lt_gt($cgi, $_) ]
+ foreach qw( total_setup total_recur );
+
+# my($query) = $cgi->keywords;
+# if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) {
+# $search{'open'} = 1 if $1;
+# ($search{'days'}, my $field) = ($2, $3);
+# $field = "_date" if $field eq 'date';
+# $orderby = "ORDER BY cust_bill.$field";
+# }
+
+# if ( $cgi->param('newest_percust') ) {
+# $search{'newest_percust'} = 1;
+# $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'";
+# }
+
+ my $extra_sql = ' WHERE '. FS::quotation->search_sql_where( \%search );
+
+ unless ( $count_query ) {
+ $count_query = 'SELECT COUNT(*)';
+ }
+ $count_query .= " FROM quotation $join_prospect_main $join_cust_main $extra_sql";
+
+ $sql_query = {
+ 'table' => 'quotation',
+ 'addl_from' => "$join_prospect_main $join_cust_main",
+ 'hashref' => {},
+ 'select' => join(', ',
+ 'quotation.*',
+ #( map "cust_main.$_", qw(custnum last first company) ),
+ 'prospect_main.prospectnum as prospect_main_prospectnum',
+ 'cust_main.custnum as cust_main_custnum',
+ #FS::UI::Web::cust_sql_fields(),
+ ),
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ };
+
+#}
+
+my $link = [ "${p}view/quotation.html?", 'quotationnum', ];
+my $prospect_link = sub {
+ my $quotation = shift;
+ $quotation->prospect_main_prospectnum
+ ? [ "${p}view/prospect_main.html?", 'prospectnum' ]
+ : '';
+};
+
+my $cust_link = sub {
+ my $quotation = shift;
+ $quotation->cust_main_custnum
+ ? [ "${p}view/cust_main.cgi?", 'custnum' ]
+ : '';
+};
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $html_init = join("\n", map {
+ ( my $action = $_ ) =~ s/_$//;
+ include('/elements/progress-init.html',
+ $_.'form',
+ [ keys %search ],
+ "../misc/${_}invoices.cgi",
+ { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but...
+ $_, #key
+ ),
+ qq!<FORM NAME="${_}form">!,
+ ( map { my $f = $_;
+ my @values = ref($search{$f}) ? @{ $search{$f} } : $search{$f};
+ map qq!<INPUT TYPE="hidden" NAME="$f" VALUE="$_">!, @values;
+ }
+ keys %search
+ ),
+ qq!</FORM>!
+} qw( print_ email_ fax_ ftp_ spool_ ) ).
+
+'<SCRIPT TYPE="text/javascript">
+
+function confirm_print_process() {
+ if ( ! confirm('.js_mt("Are you sure you want to reprint these invoices?").') ) {
+ return;
+ }
+ print_process();
+}
+function confirm_email_process() {
+ if ( ! confirm('.js_mt("Are you sure you want to re-email these invoices?").') ) {
+ return;
+ }
+ email_process();
+}
+function confirm_fax_process() {
+ if ( ! confirm('.js_mt("Are you sure you want to re-fax these invoices?").') ) {
+ return;
+ }
+ fax_process();
+}
+function confirm_ftp_process() {
+ if ( ! confirm('.js_mt("Are you sure you want to re-FTP these invoices?").') ) {
+ return;
+ }
+ ftp_process();
+}
+function confirm_spool_process() {
+ if ( ! confirm('.js_mt("Are you sure you want to re-spool these invoices?").') ) {
+ return;
+ }
+ spool_process();
+}
+
+</SCRIPT>';
+
+my $menubar = [];
+
+#if ( $curuser->access_right('Resend quotations') ) {
+#
+# push @$menubar, emt('Print these invoices') =>
+# "javascript:confirm_print_process()",
+# emt('Email these invoices') =>
+# "javascript:confirm_email_process()";
+#
+# push @$menubar, emt('Fax these invoices') =>
+# "javascript:confirm_fax_process()"
+# if $conf->exists('hylafax');
+#
+# push @$menubar, emt('FTP these invoices') =>
+# "javascript:confirm_ftp_process()"
+# if $conf->exists('cust_bill-ftpformat');
+#
+# push @$menubar, emt('Spool these invoices') =>
+# "javascript:confirm_spool_process()"
+# if $conf->exists('cust_bill-spoolformat');
+#
+#}
+
+</%init>
diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html
index c9d97c5eb..f593a94d8 100755
--- a/httemplate/search/report_477.html
+++ b/httemplate/search/report_477.html
@@ -17,6 +17,18 @@
)
%>
+% # not tr-select-state, we only want to choose from among those that
+% # have customers
+ <& /elements/tr-select-table.html,
+ 'label' => 'State',
+ 'field' => 'state',
+ 'table' => 'cust_location',
+ 'name_col' => 'state',
+ 'value_col' => 'state',
+ 'disable_empty' => 1,
+ 'records' => \@states,
+ &>
+
<% include( '/elements/tr-select-pkg_class.html',
'multiple' => 1,
'empty_label' => '(empty class)',
@@ -252,4 +264,10 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+my @states = qsearch({
+ 'table' => 'cust_location',
+ 'select' => 'DISTINCT(state)',
+ 'hashref' => { 'country' => 'US' }, # 477 report isn't relevant elsewhere
+});
+
</%init>
diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html
index e3418a7d4..0e1693b9c 100644
--- a/httemplate/search/report_cdr.html
+++ b/httemplate/search/report_cdr.html
@@ -24,9 +24,12 @@
<SELECT NAME="freesidestatus">
<OPTION VALUE="">(all)</OPTION>
<OPTION VALUE="NULL">unprocessed</OPTION>
+%# <OPTION VALUE="processing-tiered">processing</OPTION>
<OPTION VALUE="rated">prerated
- <OPTION VALUE="done">processed</OPTION>
- <OPTION VALUE="failed">skipped</OPTION>
+ <OPTION VALUE="no-charge">processed (included)</OPTION>
+ <OPTION VALUE="done">processed (billed)</OPTION>
+ <OPTION VALUE="skipped">skipped</OPTION>
+ <OPTION VALUE="failed">failed</OPTION>
</SELECT>
</TD>
</TR>
diff --git a/httemplate/search/report_cust_bill_pkg_referral.html b/httemplate/search/report_cust_bill_pkg_referral.html
new file mode 100644
index 000000000..b4716d4fc
--- /dev/null
+++ b/httemplate/search/report_cust_bill_pkg_referral.html
@@ -0,0 +1,66 @@
+<% include('/elements/header.html', 'Sales Report with Advertising Source' ) %>
+
+<FORM ACTION="cust_bill_pkg_referral.html" METHOD="GET">
+
+<TABLE>
+
+<& /elements/tr-input-beginning_ending.html &>
+
+<& /elements/tr-select-agent.html,
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ 'empty_label' => 'all',
+&>
+
+<& /elements/tr-select-cust_pkg-status.html,
+ 'label' => 'Package status',
+ 'multiple' => 1,
+ 'disable_empty' => 1,
+&>
+
+<& /elements/tr-select-part_referral.html,
+ 'multiple' => 1,
+ 'disable_empty' => 1,
+&>
+
+<& /elements/tr-select-pkg_class.html,
+ 'pre_options' => [ '' => 'all', '0' => '(empty class)' ],
+ 'disable_empty' => 1,
+&>
+
+<& /elements/tr-select-table.html,
+ 'label' => 'Report classes',
+ 'table' => 'part_pkg_report_option',
+ 'name_col' => 'name',
+ 'hashref' => { disabled => '' },
+ 'element_name' => 'report_option',
+ 'multiple' => 1,
+&>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="setup" VALUE="1" CHECKED></TD>
+ <TD>Show setup/one-time fees</TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="recur" VALUE="1" CHECKED></TD>
+ <TD>Show recurring fees</TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="usage" VALUE="1" CHECKED></TD>
+ <TD>Show usage charges</TD>
+</TR>
+
+</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/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html
index 00cb9ed2c..8bad332a9 100644
--- a/httemplate/search/report_cust_main-zip.html
+++ b/httemplate/search/report_cust_main-zip.html
@@ -8,8 +8,8 @@
<TD ALIGN="right">Billing or service zip</TD>
<TD>
<SELECT NAME="column">
- <OPTION VALUE="zip">Billing zip
- <OPTION VALUE="ship_zip">Service zip
+ <OPTION VALUE="bill">Billing zip
+ <OPTION VALUE="ship">Service zip
</SELECT>
</TD>
</TR>
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index 0ef5a5196..3e7181d4f 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -25,10 +25,22 @@
'all_selected' => 1,
&>
+ <& /elements/tr-select-part_referral.html,
+ 'label' => emt('Advertising Source'),
+ 'multiple' => 1,
+ #no, causes customers with disabled ones to disappear
+ #'all_selected' => 1,
+ &>
+
<TR>
<TD ALIGN="right" VALIGN="center"><% mt('Address') |h %></TD>
<TD><INPUT TYPE="text" NAME="address" SIZE=54></TD>
</TR>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('Zip') |h %></TD>
+ <TD><INPUT TYPE="text" NAME="zip" SIZE=12></TD>
+ </TR>
<TR>
<TD ALIGN="right" VALIGN="center"><% mt('Signup date') |h %></TD>
@@ -42,6 +54,48 @@
</TD>
</TR>
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('Date of Birth') |h %></TD>
+ <TD>
+ <TABLE>
+ <& /elements/tr-input-beginning_ending.html,
+ prefix => 'birthdate',
+ layout => 'horiz',
+ &>
+ </TABLE>
+ </TD>
+ </TR>
+% }
+
+% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('Spouse Date of Birth') |h %></TD>
+ <TD>
+ <TABLE>
+ <& /elements/tr-input-beginning_ending.html,
+ prefix => 'spouse_birthdate',
+ layout => 'horiz',
+ &>
+ </TABLE>
+ </TD>
+ </TR>
+% }
+
+% if ( $conf->exists('cust_main-enable_anniversary_date') ) {
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('Anniversary Date') |h %></TD>
+ <TD>
+ <TABLE>
+ <& /elements/tr-input-beginning_ending.html,
+ prefix => 'anniversary_date',
+ layout => 'horiz',
+ &>
+ </TABLE>
+ </TD>
+ </TR>
+% }
+
<& /elements/tr-select-cust_tag.html,
'cgi' => $cgi,
'is_report' => 1,
@@ -101,11 +155,6 @@
&>
<TR>
- <TD ALIGN="right" VALIGN="center"><% mt('Include cancelled packages') |h %></TD>
- <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD>
- </TR>
-
- <TR>
<TD ALIGN="right" VALIGN="center"><% mt('Without census tract') |h %></TD>
<TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD>
</TR>
@@ -132,6 +181,12 @@
<TD ALIGN="right" VALIGN="center"><% mt('Add package columns') |h %></TD>
<TD><INPUT TYPE="checkbox" NAME="flattened_pkgs"></TD>
</TR>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="center"><% mt('Include cancelled packages') |h %></TD>
+ <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD>
+ </TR>
+
</TABLE>
<BR>
@@ -143,9 +198,7 @@
<%init>
die "access denied"
- unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') &&
- $FS::CurrentUser::CurrentUser->access_right('List packages')
- );
+ unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search');
my $conf = new FS::Conf;
diff --git a/httemplate/search/report_customer_accounting_summary.html b/httemplate/search/report_customer_accounting_summary.html
index d20f756c8..f2a13a27b 100755
--- a/httemplate/search/report_customer_accounting_summary.html
+++ b/httemplate/search/report_customer_accounting_summary.html
@@ -11,6 +11,14 @@
)
%>
+ <% include( '/elements/tr-select-part_referral.html',
+ 'curr_value' => scalar( $cgi->param('refnum') ),
+ 'label' => 'Advertising source ',
+ 'disable_empty' => 0,
+ 'empty_label' => 'all',
+ )
+ %>
+
<% include('/elements/tr-select-from_to.html' ) %>
<% include( '/elements/tr-select-cust_main-status.html',
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
index 061b24c68..4743e2d21 100644
--- a/httemplate/search/report_prepaid_income.html
+++ b/httemplate/search/report_prepaid_income.html
@@ -2,7 +2,7 @@
<% include('/elements/init_calendar.html') %>
-<FORM ACTION="report_prepaid_income.cgi" METHOD="GET">
+<FORM ACTION="prepaid_income.html" METHOD="GET">
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
@@ -13,7 +13,7 @@
</TR>
<TR>
- <TD>As of </TD>
+ <TD ALIGN="right">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">
@@ -30,7 +30,20 @@
</TR>
<% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
-
+
+ <& /elements/tr-select-cust_main-status.html,
+ label => mt('Customer Status') &>
+ <& /elements/tr-select.html,
+ label => 'Invoice Status',
+ field => 'mode',
+ options => [ qw(billed paid) ] &>
+ <TR>
+ <TD ALIGN="right">
+ <INPUT TYPE="checkbox" NAME="include_monthly" VALUE=1>
+ </TD>
+ <TD ALIGN="left"><% mt('Include packages with period &le; 1 month') %>
+ </TD>
+ </TR>
<TR>
<TD COLSPAN=2>&nbsp;</TD>
</TR>
diff --git a/httemplate/search/report_quotation.html b/httemplate/search/report_quotation.html
new file mode 100644
index 000000000..1be904dc3
--- /dev/null
+++ b/httemplate/search/report_quotation.html
@@ -0,0 +1,75 @@
+<& /elements/header.html, mt($title, @title_arg) &>
+
+<FORM ACTION="quotation.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+<INPUT TYPE="hidden" NAME="prospectnum" VALUE="<% $prospectnum %>">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+
+% unless ( $custnum ) {
+ <& /elements/tr-select-agent.html,
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => emt('Quotations for agent: '),
+ 'disable_empty' => 0,
+ &>
+% }
+
+ <& /elements/tr-input-beginning_ending.html &>
+
+ <& /elements/tr-input-lessthan_greaterthan.html,
+ label => emt('Setup'),
+ field => 'total_setup',
+ &>
+
+ <& /elements/tr-input-lessthan_greaterthan.html,
+ label => emt('Recurring'),
+ field => 'total_recur',
+ &>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('List quotations');
+
+my $conf = new FS::Conf;
+
+my $title = 'Quotation Report';
+#false laziness w/report_cust_pkg.html
+my @title_arg = ();
+
+my $prospectnum = '';
+my $prospect_main = '';
+if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ $prospectnum = $1;
+ $prospect_main = qsearchs({
+ 'table' => 'prospect_main',
+ 'hashref' => { 'prospectnum' => $prospectnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or die "unknown prospectnum $prospectnum";
+ $title .= ': [_1]';
+ push @title_arg, $prospect_main->name;
+}
+
+my $custnum = '';
+my $cust_main = '';
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+ $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+ }) or die "unknown custnum $custnum";
+ $title .= ': [_1]';
+ push @title_arg, $cust_main->name;
+}
+
+</%init>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
index 19f863525..5cff0f4fc 100755
--- a/httemplate/search/report_receivables.html
+++ b/httemplate/search/report_receivables.html
@@ -52,7 +52,7 @@ function toggle(obj) {
<& /elements/tr-input-date-field.html, {
'name' => 'as_of',
'value' => time,
- 'label' => emt('As of date '),
+ 'label' => emt('At the end of date '),
'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y',
}
&>
diff --git a/httemplate/search/report_rt_ticket.html b/httemplate/search/report_rt_ticket.html
index f0d7a4200..a4ceaa6a4 100644
--- a/httemplate/search/report_rt_ticket.html
+++ b/httemplate/search/report_rt_ticket.html
@@ -59,7 +59,6 @@ if ( @pkgparts ) {
}
# get a list of TimeValue-type custom fields
-RT::Init();
my $CurrentUser = RT::CurrentUser->new();
$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
die "RT not configured" unless $CurrentUser->id;
diff --git a/httemplate/search/report_sqlradius_usage.html b/httemplate/search/report_sqlradius_usage.html
new file mode 100644
index 000000000..01215e834
--- /dev/null
+++ b/httemplate/search/report_sqlradius_usage.html
@@ -0,0 +1,40 @@
+<& /elements/header.html, mt($title) &>
+
+<FORM ACTION="sqlradius_usage.html" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0
+
+<& /elements/tr-select-agent.html,
+ 'empty_label' => 'all',
+&>
+
+% my @exporttypes = map { "'$_'" } qw(sqlradius broadband_sqlradius);
+<& /elements/tr-select-table.html,
+ 'label' => 'Export',
+ 'table' => 'part_export',
+ 'name_col' => 'label',
+ 'hashref' => {},
+ 'extra_sql' => ' WHERE exporttype IN('.join(',', @exporttypes).')',
+ 'disable_empty' => 1,
+ 'order_by' => 'ORDER BY exportnum',
+&>
+
+<& /elements/tr-input-beginning_ending.html &>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Usage: RADIUS sessions');
+ # yes?
+
+my $title = 'Data Usage Report';
+
+</%init>
diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html
index 14c284fb7..74bf5538e 100755
--- a/httemplate/search/report_svc_acct.html
+++ b/httemplate/search/report_svc_acct.html
@@ -114,7 +114,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #?
+ unless $FS::CurrentUser::CurrentUser->access_right('Services: Accounts: Advanced search'); #?
my $title = emt('Account Report');
diff --git a/httemplate/search/report_svc_broadband.html b/httemplate/search/report_svc_broadband.html
index 37f21b79f..d7422eeae 100755
--- a/httemplate/search/report_svc_broadband.html
+++ b/httemplate/search/report_svc_broadband.html
@@ -76,7 +76,7 @@
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #?
+ unless $FS::CurrentUser::CurrentUser->access_right('Services: Wireless broadband services: Advanced search');
my $title = 'Broadband Service Report';
my $routernum = [ $cgi->param('routernum') || '' ];
diff --git a/httemplate/search/report_svc_hardware.html b/httemplate/search/report_svc_hardware.html
index 61ba4ab8a..b0bfc08c7 100755
--- a/httemplate/search/report_svc_hardware.html
+++ b/httemplate/search/report_svc_hardware.html
@@ -61,7 +61,7 @@ OR (SELECT COUNT(*) FROM svc_hardware
<%init>
die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #?
+ unless $FS::CurrentUser::CurrentUser->access_right('Services: Hardware: Advanced search');
my $title = 'Hardware Service Report';
diff --git a/httemplate/search/report_tax-xls.cgi b/httemplate/search/report_tax-xls.cgi
index 1c278dfd1..f19f85aaa 100755
--- a/httemplate/search/report_tax-xls.cgi
+++ b/httemplate/search/report_tax-xls.cgi
@@ -1,9 +1,25 @@
<% $data %>
<%init>
+my $htmldoc = include('report_tax.cgi');
+
+my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i);
+
+# do this first so we can override the format if it's too many rows
+# attribs option: how to locate the table? It's the only one with class="grid".
+my $te = HTML::TableExtract->new(attribs => {class => 'grid'});
+$te->parse($htmldoc);
+my $table = $te->first_table_found;
+
+my $override = ($table->row_count >= 65536 ? 'XLSX' : '');
+my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override);
+my $filename = 'report_tax'.$format->{extension};
+
+http_header('Content-Type' => $format->{mime_type});
+http_header('Content-Disposition' => qq!attachment;filename="$filename"! );
my $data = '';
my $XLS = new IO::Scalar \$data;
-my $workbook = Spreadsheet::WriteExcel->new($XLS)
+my $workbook = $format->{class}->new($XLS)
or die "Error opening .xls file: $!";
# hardcoded formats, this could be handled better
@@ -66,15 +82,6 @@ foreach (keys(%format)) {
}
my $ws = $workbook->add_worksheet('taxreport');
-my $htmldoc = include('report_tax.cgi');
-
-my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i);
-
-# attribs option: how to locate the table? It's the only one with class="grid".
-my $te = HTML::TableExtract->new(attribs => {class => 'grid'});
-$te->parse($htmldoc);
-my $table = $te->first_table_found;
-
my @sheet;
$sheet[0][0] = {
text => $title,
@@ -148,6 +155,4 @@ for my $x (0..scalar(@widths)-1) {
$workbook->close;
-http_header('Content-Type' => 'application/vnd.ms-excel');
-http_header('Content-Disposition' => 'attachment;filename="report_tax.xls"');
</%init>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
index 0cd652d83..42a52d154 100755
--- a/httemplate/search/report_tax.cgi
+++ b/httemplate/search/report_tax.cgi
@@ -60,9 +60,9 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
% my $link = '';
% if ( $region->{'label'} eq $out ) {
% $link = ';out=1';
-% } else {
-% $link = ';'. $region->{'url_param'}
-% if $region->{'url_param'};
+% } elsif ( $region->{'taxnums'} ) {
+% # might be nicer to specify this as country:state:city
+% $link = ';'.join(';', map { "taxnum=$_" } @{ $region->{'taxnums'} });
% }
%
% if ( $bgcolor eq $bgcolor1 ) {
@@ -71,15 +71,12 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
% $bgcolor = $bgcolor1;
% }
%
-% #my $diff = 0;
% my $hicolor = $bgcolor;
% unless ( $cgi->param('show_taxclasses') ) {
% my $diff = abs( sprintf( '%.2f', $region->{'owed'} )
% - sprintf( '%.2f', $region->{'tax'} )
% );
% if ( $diff > 0.02 ) {
-% # $hicolor = $hicolor eq '#eeeeee' ? '#eeee66' : '#ffff99';
-% #} elsif ( $diff ) {
% $hicolor = $hicolor eq '#eeeeee' ? '#eeee99' : '#ffffcc';
% }
% }
@@ -94,16 +91,19 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
<<%$td%>><% $region->{'label'} %></TD>
<<%$td%> ALIGN="right">
<A HREF="<% $baselink. $link %>;nottax=1"
- ><% &$money_sprintf( $region->{'total'} ) %></A>
+ ><% &$money_sprintf( $region->{'sales'} ) %></A>
</TD>
+% if ( $region->{'label'} eq $out ) {
+ <<%$td%> COLSPAN=12></TD>
+% } else { #not $out
<<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
<<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"
+ <A HREF="<% $baselink. $link %>;nottax=1;exempt_cust=Y"
><% &$money_sprintf( $region->{'exempt_cust'} ) %></A>
</TD>
<<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
<<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"
+ <A HREF="<% $baselink. $link %>;nottax=1;exempt_pkg=Y"
><% &$money_sprintf( $region->{'exempt_pkg'} ) %></A>
</TD>
<<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
@@ -122,12 +122,24 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
<<%$tdh%> ALIGN="right">
<% &$money_sprintf( $region->{'owed'} ) %>
</TD>
-
-% unless ( $cgi->param('show_taxclasses') ) {
+% } # if !$out
+% unless ( $cgi->param('show_taxclasses') ) {
% my $invlink = $region->{'url_param_inv'}
% ? ';'. $region->{'url_param_inv'}
% : $link;
+% if ( $region->{'label'} eq $out ) {
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $invlink %>;istax=1"
+ ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A>
+ </TD>
+ <<%$td%>></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $creditlink. $invlink %>;istax=1"
+ ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A>
+ </TD>
+ <<%$td%> COLSPAN=2></TD>
+% } else { #not $out
<<%$tdh%> ALIGN="right">
<A HREF="<% $baselink. $invlink %>;istax=1"
><% &$money_sprintf( $region->{'tax'} ) %></A>
@@ -141,7 +153,8 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
<<%$tdh%> ALIGN="right">
<% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
</TD>
-% }
+% }
+% } # not $out
</TR>
% }
@@ -190,6 +203,18 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
<TR>
<<%$td%>><% $region->{'label'} %></TD>
+% if ( $region->{'label'} eq $out ) {
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $baselink. $invlink %>;istax=1"
+ ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A>
+ </TD>
+ <<%$td%>></TD>
+ <<%$td%> ALIGN="right">
+ <A HREF="<% $creditlink. $invlink %>;istax=1"
+ ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A>
+ </TD>
+ <<%$td%> COLSPAN=2></TD>
+% } else { #not $out
<<%$td%> ALIGN="right">
<A HREF="<% $baselink. $link %>;istax=1"
><% &$money_sprintf( $region->{'tax'} ) %></A>
@@ -204,36 +229,12 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
<% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
</TD>
</TR>
-
-% }
-
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
-
- <TR>
- <<%$td%>>Total</TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink %>;istax=1"
- ><% &$money_sprintf( $tot_tax ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $creditlink %>;istax=1"
- ><% &$money_sprintf( $tot_credit ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <% &$money_sprintf( $tot_tax - $tot_credit ) %>
- </TD>
- </TR>
+% } # if $out
+% } #foreach $region
</TABLE>
-% }
+% } # if show_taxclasses
<% include('/elements/footer.html') %>
@@ -242,26 +243,38 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $DEBUG = $cgi->param('debug') || 0;
+
my $conf = new FS::Conf;
-my $user = getotaker;
+my $out = 'Out of taxable region(s)';
+
+my %label_opt = ( out => 1 ); #enable 'Out of Taxable Region' label
+$label_opt{no_city} = 1 unless $cgi->param('show_cities');
+$label_opt{no_taxclass} = 1 unless $cgi->param('show_taxclasses');
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
my $join_cust = ' JOIN cust_bill USING ( invnum )
LEFT JOIN cust_main USING ( custnum ) ';
+
my $join_cust_pkg = $join_cust.
' LEFT JOIN cust_pkg USING ( pkgnum )
LEFT JOIN part_pkg USING ( pkgpart ) ';
-$join_cust_pkg .= ' LEFT JOIN cust_location USING ( locationnum )'
- if $conf->exists('tax-pkg_address');
my $from_join_cust_pkg = " FROM cust_bill_pkg $join_cust_pkg ";
-my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+# either or both of these can be used to link cust_bill_pkg to cust_main_county
+my $pkg_tax = "SELECT SUM(amount) as tax_amount, invnum, taxnum, ".
+ "cust_bill_pkg_tax_location.pkgnum ".
+ "FROM cust_bill_pkg_tax_location JOIN cust_bill_pkg USING (billpkgnum) ".
+ "GROUP BY billpkgnum, invnum, taxnum, cust_bill_pkg_tax_location.pkgnum";
-my( $location_sql, @base_param ) = FS::cust_pkg->location_sql;
-$where .= " AND $location_sql ";
+my $pkg_tax_exempt = "SELECT SUM(amount) AS exempt_charged, billpkgnum, taxnum ".
+ "FROM cust_tax_exempt_pkg EXEMPT_WHERE GROUP BY billpkgnum, taxnum";
+
+my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+my $group = "GROUP BY cust_main_county.taxnum";
my $agentname = '';
if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
@@ -271,304 +284,188 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
$where .= ' AND cust_main.agentnum = '. $agent->agentnum;
}
-sub gotcust {
- my $table = shift;
- my $prefix = @_ ? shift : '';
- "
- ( $table.${prefix}city = cust_main_county.city
- OR cust_main_county.city = ''
- OR cust_main_county.city IS NULL )
- AND ( $table.${prefix}county = cust_main_county.county
- OR cust_main_county.county = ''
- OR cust_main_county.county IS NULL )
- AND ( $table.${prefix}state = cust_main_county.state
- OR cust_main_county.state = ''
- OR cust_main_county.state IS NULL )
- AND ( $table.${prefix}country = cust_main_county.country )
- ";
-}
-
-my $gotcust;
-if ( $conf->exists('tax-ship_address') ) {
-
- $gotcust = "
- ( cust_main_county.country = cust_main.country
- OR cust_main_county.country = cust_main.ship_country
- )
-
- AND
-
- (
- ( ( ship_last IS NULL OR ship_last = '' )
- AND ". gotcust('cust_main'). "
- )
- OR
- ( ship_last IS NOT NULL AND ship_last != ''
- AND ". gotcust('cust_main', 'ship_'). "
- )
- )
- ";
-
-} else {
-
- $gotcust = gotcust('cust_main');
-
+my $nottax = 'cust_bill_pkg.pkgnum != 0';
+
+# one query for each column of the report
+# plus separate queries for the totals row
+my (%sql, %all_sql);
+
+# general form
+my $exempt = "SELECT cust_main_county.taxnum, SUM(exempt_charged)
+ FROM cust_main_county
+ JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
+ USING (taxnum)
+ JOIN cust_bill_pkg USING (billpkgnum)
+ $join_cust $where AND $nottax $group";
+
+my $all_exempt = "SELECT SUM(exempt_charged)
+ FROM cust_main_county
+ JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
+ USING (taxnum)
+ JOIN cust_bill_pkg USING (billpkgnum)
+ $join_cust $where AND $nottax";
+
+# sales to tax-exempt customers
+$sql{exempt_cust} = $exempt;
+$sql{exempt_cust} =~ s/EXEMPT_WHERE/WHERE exempt_cust = 'Y' OR exempt_cust_taxname = 'Y'/;
+$all_sql{exempt_cust} = $all_exempt;
+$all_sql{exempt_cust} =~ s/EXEMPT_WHERE/WHERE exempt_cust = 'Y' OR exempt_cust_taxname = 'Y'/;
+
+# sales of tax-exempt packages
+$sql{exempt_pkg} = $exempt;
+$sql{exempt_pkg} =~ s/EXEMPT_WHERE/WHERE exempt_setup = 'Y' OR exempt_recur = 'Y'/;
+$all_sql{exempt_pkg} = $all_exempt;
+$all_sql{exempt_pkg} =~ s/EXEMPT_WHERE/WHERE exempt_setup = 'Y' OR exempt_recur = 'Y'/;
+
+# monthly per-customer exemptions
+$sql{exempt_monthly} = $exempt;
+$sql{exempt_monthly} =~ s/EXEMPT_WHERE/WHERE exempt_monthly = 'Y'/;
+$all_sql{exempt_monthly} = $all_exempt;
+$all_sql{exempt_monthly} =~ s/EXEMPT_WHERE/WHERE exempt_monthly = 'Y'/;
+
+# taxable sales
+$sql{taxable} = "SELECT cust_main_county.taxnum,
+ SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(exempt_charged, 0))
+ FROM cust_main_county
+ JOIN ($pkg_tax) AS pkg_tax USING (taxnum)
+ JOIN cust_bill_pkg USING (invnum, pkgnum)
+ LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
+ ON (pkg_tax_exempt.billpkgnum = cust_bill_pkg.billpkgnum
+ AND pkg_tax_exempt.taxnum = cust_main_county.taxnum)
+ $join_cust $where AND $nottax $group";
+
+# Here we're going to sum all line items that are taxable _at all_,
+# under any tax. exempt_charged is the sum of all exemptions for a
+# particular billpkgnum + taxnum; we take the taxnum that has the
+# smallest sum of exemptions and subtract that from the charged amount.
+$all_sql{taxable} = "SELECT
+ SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(min_exempt, 0))
+ FROM cust_bill_pkg
+ JOIN (
+ SELECT invnum, pkgnum, MIN(exempt_charged) AS min_exempt
+ FROM ($pkg_tax) AS pkg_tax
+ JOIN cust_bill_pkg USING (invnum, pkgnum)
+ LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum, taxnum)
+ GROUP BY invnum, pkgnum
+ ) AS pkg_is_taxable
+ USING (invnum, pkgnum)
+ $join_cust $where AND $nottax";
+ # we don't join pkg_tax_exempt.taxnum here, because
+
+$sql{taxable} =~ s/EXEMPT_WHERE//; # unrestricted
+$all_sql{taxable} =~ s/EXEMPT_WHERE//;
+
+# there isn't one for 'sales', because we calculate sales by adding up
+# the taxable and exempt columns.
+
+# sum of billed tax:
+# join cust_bill_pkg to cust_main_county via cust_bill_pkg_tax_location
+my $taxfrom = " FROM cust_bill_pkg
+ $join_cust
+ LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
+ LEFT JOIN cust_main_county USING ( taxnum )";
+
+my $istax = "cust_bill_pkg.pkgnum = 0";
+my $named_tax = "(
+ taxname = itemdesc
+ OR ( taxname IS NULL
+ AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )
+ )
+)";
+
+$sql{tax} = "SELECT cust_main_county.taxnum,
+ SUM(cust_bill_pkg_tax_location.amount)
+ $taxfrom
+ $where AND $istax AND $named_tax
+ $group";
+
+$all_sql{tax} = "SELECT SUM(cust_bill_pkg.setup)
+ FROM cust_bill_pkg
+ $join_cust
+ $where AND $istax";
+
+# sum of credits applied against billed tax
+my $creditfrom = $taxfrom .
+ ' JOIN cust_credit_bill_pkg USING (billpkgtaxlocationnum)';
+my $creditfromwhere = $where .
+ ' AND billpkgtaxratelocationnum IS NULL';
+
+$sql{credit} = "SELECT cust_main_county.taxnum,
+ SUM(cust_credit_bill_pkg.amount)
+ $creditfrom
+ $creditfromwhere AND $istax AND $named_tax
+ $group";
+
+$all_sql{credit} = "SELECT SUM(cust_credit_bill_pkg.amount)
+ FROM cust_credit_bill_pkg
+ JOIN cust_bill_pkg USING (billpkgnum)
+ $join_cust
+ $where AND $istax";
+
+my %data;
+my %total = (owed => 0);
+foreach my $k (keys(%sql)) {
+ my $stmt = $sql{$k};
+ warn "\n".uc($k).":\n".$stmt."\n" if $DEBUG;
+ my $sth = dbh->prepare($stmt);
+ # two columns => key/value
+ $sth->execute
+ or die "failed to execute $k query: ".$sth->errstr;
+ $data{$k} = +{ map { @$_ } @{ $sth->fetchall_arrayref([]) } };
+
+ warn "\n".$all_sql{$k}."\n" if $DEBUG;
+ $total{$k} = FS::Record->scalar_sql( $all_sql{$k} );
+ warn Dumper($data{$k}) if $DEBUG > 1;
}
-if ( $conf->exists('tax-pkg_address') ) {
- $gotcust = "
- ( cust_pkg.locationnum IS NULL AND $gotcust)
- OR ( cust_pkg.locationnum IS NOT NULL AND ". gotcust('cust_location'). " )";
- $gotcust =
- "WHERE 0 < ( SELECT COUNT(*) FROM cust_pkg
- LEFT JOIN cust_main USING ( custnum )
- LEFT JOIN cust_location USING ( locationnum )
- WHERE $gotcust
- LIMIT 1
- )
- ";
-} else {
- $gotcust =
- "WHERE 0 < ( SELECT COUNT(*) FROM cust_main WHERE $gotcust LIMIT 1 )";
-}
-
-my $out = 'Out of taxable region(s)';
-my %regions = ();
-
-foreach my $r ( qsearch({ 'table' => 'cust_main_county',
- 'extra_sql' => $gotcust,
- })
- )
-{
- #warn $r->county. ' '. $r->state. ' '. $r->country. "\n";
-
- my $label = getlabel($r);
- $regions{$label}->{'label'} = $label;
-
- $regions{$label}->{$_} = $r->$_() for (qw( county state country )); #taxname?
-
- my @url_param = qw( county state country taxname );
- push @url_param, 'city' if $cgi->param('show_cities') && $r->city();
-
- $regions{$label}->{'url_param'} =
- join(';', map "$_=".uri_escape($r->$_()), @url_param );
-
- my @param = @base_param;
- my $mywhere = $where;
-
- if ( $r->taxclass ) {
-
- $mywhere .= " AND taxclass = ? ";
- push @param, 'taxclass';
- $regions{$label}->{'url_param'} .= ';taxclass='. uri_escape($r->taxclass);
- #no, always# if $cgi->param('show_taxclasses');
-
- $regions{$label}->{'taxclass'} = $r->taxclass;
-
- } else {
-
- my $same_sql = $r->sql_taxclass_sameregion;
- $mywhere .= " AND $same_sql" if $same_sql;
-
- $regions{$label}->{'url_param'} .= ';taxclassNULL=1'
- if $cgi->param('show_taxclasses')
- || $same_sql;
-
- }
-
- my $fromwhere = "$from_join_cust_pkg $mywhere"; # AND payby != 'COMP' ";
-
-# my $label = getlabel($r);
-# $regions{$label}->{'label'} = $label;
-
- my $nottax = 'pkgnum != 0';
-
- ## calculate total for this region
-
- my $t_sql =
- "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax";
- my $t = scalar_sql($r, \@param, $t_sql);
- $regions{$label}->{'total'} += $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';
- #}
-
- ## calculate customer-exemption for this region
-
-## my $taxable = $t;
-
-# my($taxable, $x_cust) = (0, 0);
-# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i }
-# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) {
-# $taxable += scalar_sql($r, \@param,
-# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )"
-# );
-#
-# $x_cust += scalar_sql($r, \@param,
-# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'"
-# );
-# }
-
- #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 $cust_exempt "
- );
-
- $regions{$label}->{'exempt_cust'} += $x_cust;
-
- ## calculate package-exemption for this region
-
- my $x_pkg = scalar_sql($r, \@param,
- "SELECT SUM(
- ( CASE WHEN part_pkg.setuptax = 'Y'
- THEN cust_bill_pkg.setup
- ELSE 0
- END
- )
- +
- ( CASE WHEN part_pkg.recurtax = 'Y'
- THEN cust_bill_pkg.recur
- ELSE 0
- END
- )
- )
- $fromwhere
- AND $nottax
- AND (
- ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 )
- OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 )
- )
- AND ( tax != 'Y' OR tax IS NULL )
- "
- );
- $regions{$label}->{'exempt_pkg'} += $x_pkg;
-
- ## calculate monthly exemption (texas tax) for this region
-
- # count up all the cust_tax_exempt_pkg records associated with
- # the actual line items.
-
- my $x_monthly = scalar_sql($r, \@param,
- "SELECT SUM(amount)
- FROM cust_tax_exempt_pkg
- JOIN cust_bill_pkg USING ( billpkgnum )
- $join_cust_pkg
- $mywhere"
- );
- $regions{$label}->{'exempt_monthly'} += $x_monthly;
-
- my $taxable = $t - $x_cust - $x_pkg - $x_monthly;
- $regions{$label}->{'taxable'} += $taxable;
-
- $regions{$label}->{'owed'} += $taxable * ($r->tax/100);
-
- if ( defined($regions{$label}->{'rate'})
- && $regions{$label}->{'rate'} != $r->tax.'%' ) {
- $regions{$label}->{'rate'} = 'variable';
- } else {
- $regions{$label}->{'rate'} = $r->tax.'%';
- }
-
-}
-
-my $distinct = "country, state, county, city,
- CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname";
-my $taxclass_distinct =
- #a little bit unsure of this part... test?
- #ah, it looks like it winds up being irrelevant as ->{'tax'}
- # from $regions is not displayed when show_taxclasses is on
- ( $cgi->param('show_taxclasses')
- ? " CASE WHEN taxclass IS NULL THEN '' ELSE taxclass END "
- : " '' "
- )." AS taxclass";
-
-
-my %qsearch = (
- 'select' => "DISTINCT $distinct, $taxclass_distinct",
- 'table' => 'cust_main_county',
- 'hashref' => {},
- 'extra_sql' => $gotcust,
+# so $data{tax}, for example, is now a hash with one entry
+# for each taxnum, containing the tax billed on that taxnum.
+
+# oddball cases:
+# "out of taxable region" sales
+my %out;
+my $out_sales_sql =
+ "SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
+ FROM (cust_bill_pkg $join_cust)
+ LEFT JOIN ($pkg_tax) AS pkg_tax USING (invnum, pkgnum)
+ LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum)
+ $where AND $nottax
+ AND pkg_tax.taxnum IS NULL AND pkg_tax_exempt.taxnum IS NULL"
+;
+
+$out_sales_sql =~ s/EXEMPT_WHERE//;
+
+$out{sales} = FS::Record->scalar_sql($out_sales_sql);
+
+# unlinked tax collected (for diagnostics)
+my $out_tax_sql =
+ "SELECT SUM(cust_bill_pkg.setup)
+ FROM (cust_bill_pkg $join_cust)
+ LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
+ $where AND $istax AND cust_bill_pkg_tax_location.billpkgnum IS NULL"
+;
+$out{tax} = FS::Record->scalar_sql($out_tax_sql);
+# unlinked tax credited (for diagnostics)
+my $out_credit_sql =
+ "SELECT SUM(cust_credit_bill_pkg.amount)
+ FROM cust_credit_bill_pkg
+ JOIN cust_bill_pkg USING (billpkgnum)
+ $join_cust
+ $where AND $istax AND cust_credit_bill_pkg.billpkgtaxlocationnum IS NULL"
+;
+$out{credit} = FS::Record->scalar_sql($out_credit_sql);
+
+# all sales
+$total{sales} = FS::Record->scalar_sql(
+ "SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
+ FROM cust_bill_pkg $join_cust $where AND $nottax"
);
-my $taxfromwhere = " FROM cust_bill_pkg $join_cust ";
-my $taxwhere = $where;
-if ( $conf->exists('tax-pkg_address') ) {
-
- $taxfromwhere .= 'LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
- LEFT JOIN cust_location USING ( locationnum ) ';
-
- #quelle kludge
- $taxwhere =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g;
-
-}
-my $creditfromwhere = $taxfromwhere.
- " JOIN cust_credit_bill_pkg USING (billpkgnum";
-$creditfromwhere .= " ,billpkgtaxlocationnum"
- if $conf->exists('tax-pkg_address');
-$creditfromwhere .= ")";
-
-$taxfromwhere .= " $taxwhere "; #AND payby != 'COMP' ";
-$creditfromwhere .= " $taxwhere AND billpkgtaxratelocationnum IS NULL"; #AND payby != 'COMP' ";
-
-my @taxparam = @base_param;
-
-
-#should i be a cust_main_county method or something
-#need to pass in $taxfromwhere & @taxparam???
-my $_taxamount_sub = sub {
- my $r = shift;
-
- #match itemdesc if necessary!
- my $named_tax =
- $r->taxname
- ? 'AND itemdesc = '. dbh->quote($r->taxname)
- : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
-
- my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ".
- " $taxfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax";
-
- scalar_sql($r, \@taxparam, $sql );
-};
-
-my $_creditamount_sub = sub {
- my $r = shift;
-
- #match itemdesc if necessary!
- my $named_tax =
- $r->taxname
- ? 'AND itemdesc = '. dbh->quote($r->taxname)
- : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )";
-
- my $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ".
- " $creditfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax";
-
- 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 $group_test = sub { # to be applied to a tax label
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
@@ -582,86 +479,83 @@ my $group_test = sub {
}
};
+# if show_taxclasses is on, %base_regions will contain the same data
+# as %regions, but with taxclasses merged together (and ignoring report_group
+# filtering).
+my (%regions, %base_regions);
my $tot_tax = 0;
my $tot_credit = 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 @loc_params = qw(country state county);
+push @loc_params, qw(city district) if $cgi->param('show_cities');
+
+foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) {
+ my $taxnum = $r->taxnum;
+ # set up a %regions entry for this region's tax label
+ my $label = $r->label(%label_opt);
+ next if $label eq $out;
+ $regions{$label} ||= { label => $label };
+
+ $regions{$label}->{$_} = $r->get($_) foreach @loc_params;
+ $regions{$label}->{taxnums} ||= [];
+ push @{ $regions{$label}->{taxnums} }, $r->taxnum;
+
+ my %x; # keys are data items (like 'tax', 'exempt_cust', etc.)
+ foreach my $k (keys %data) {
+ next unless exists($data{$k}->{$taxnum});
+ $x{$k} = $data{$k}->{$taxnum};
+ $regions{$label}->{$k} += $x{$k};
+ if ( $k eq 'taxable' or $k =~ /^exempt/ ) {
+ $regions{$label}->{'sales'} += $x{$k};
+ }
}
- #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' ";
- #my @param = @base_param;
+ my $owed = $data{'taxable'}->{$taxnum} * ($r->tax/100);
+ $regions{$label}->{'owed'} += $owed;
+ $total{'owed'} += $owed;
- my $x = &{$_taxamount_sub}($r);
-
- $regions{$label}->{'tax'} += $x;
- $tot_tax += $x unless $cgi->param('show_taxclasses');
-
- ## calculate credit for this region
-
- $x = &{$_creditamount_sub}($r);
-
- $regions{$label}->{'credit'} += $x;
- $tot_credit += $x unless $cgi->param('show_taxclasses');
-
-}
-
-my %base_regions = ();
-if ( $cgi->param('show_taxclasses') ) {
-
- $qsearch{'select'} = "DISTINCT $distinct";
- foreach my $r ( qsearch(\%qsearch) ) {
-
- my $x = &{$_taxamount_sub}($r);
-
- my $base_label = getlabel($r, 'no_taxclass'=>1 );
- $base_regions{$base_label}->{'label'} = $base_label;
-
- $base_regions{$base_label}->{'url_param'} =
- join(';', map "$_=". uri_escape($r->$_()),
- qw( county state country taxname )
- );
-
- $base_regions{$base_label}->{'tax'} += $x;
- $tot_tax += $x;
-
- ## calculate credit for this region
-
- $x = &{$_creditamount_sub}($r);
-
- $base_regions{$base_label}->{'credit'} += $x;
- $tot_credit += $x;
+ if ( defined($regions{$label}->{'rate'})
+ && $regions{$label}->{'rate'} != $r->tax.'%' ) {
+ $regions{$label}->{'rate'} = 'variable';
+ } else {
+ $regions{$label}->{'rate'} = $r->tax.'%';
+ }
+ if ( $cgi->param('show_taxclasses') ) {
+ my $base_label = $r->label(%label_opt, 'no_taxclass' => 1);
+ $base_regions{$base_label} ||=
+ {
+ label => $base_label,
+ tax => 0,
+ credit => 0,
+ };
+ $base_regions{$base_label}->{tax} += $x{tax};
+ $base_regions{$base_label}->{credit} += $x{credit};
}
}
-my @regions = keys %regions;
+my @regions = map { $_->{label} }
+ sort {
+ ($b eq $out) <=> ($a eq $out)
+ or $a->{country} cmp $b->{country}
+ or $a->{state} cmp $b->{state}
+ or $a->{county} cmp $b->{county}
+ or $a->{city} cmp $b->{city}
+ }
+ grep { $_->{sales} > 0 or $_->{tax} > 0 or $_->{credit} > 0 }
+ values %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, $tot_credit ) = ( 0, 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'};
- $tot_credit += $regions{$_}->{'credit'};
+foreach my $label (@regions) {
$taxclasses{$regions{$_}->{'taxclass'}} = 1
if $regions{$_}->{'taxclass'};
$county{$regions{$_}->{'county'}} = 1;
@@ -696,29 +590,27 @@ if ( $group_op ) {
#ordering
@regions =
map $regions{$_},
- sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+ sort { $a cmp $b }
@regions;
my @base_regions =
map $base_regions{$_},
- sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+ sort { $a cmp $b }
keys %base_regions;
-#add total line
-push @regions, {
- 'label' => 'Total',
- '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' => $tot_owed,
- 'tax' => $tot_tax,
- 'credit' => $tot_credit,
-};
+#add "Out of taxable" and total lines
+%out = ( %out,
+ 'label' => $out,
+ 'rate' => ''
+);
+%total = ( %total,
+ 'label' => 'Total',
+ 'url_param' => $total_url_param,
+ 'url_param_inv' => $total_url_param_invoiced,
+ 'rate' => '',
+);
+push @regions, \%out, \%total;
+push @base_regions, \%out, \%total;
#--
@@ -726,72 +618,15 @@ my $money_char = $conf->config('money_char') || '$';
my $money_sprintf = sub {
$money_char. sprintf('%.2f', shift );
};
-
-sub getlabel {
- my $r = shift;
- my %opt = @_;
-
- my $label;
- if (
- $r->tax == 0
- && ! scalar( qsearch('cust_main_county', { 'city' => $r->city,
- 'county' => $r->county,
- 'state' => $r->state,
- 'country' => $r->country,
- 'tax' => { op=>'>', value=>0 },
- }
- )
- )
-
- ) {
- #kludge to avoid "will not stay shared" warning
- my $out = 'Out of taxable region(s)';
- $label = $out;
-# } elsif ( $r->taxname && count_taxname($r->taxname) == 1 ) {
-# $label = $r->taxname;
-## $regions{$label}->{'taxname'} = $label;
-## push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country );
- } else {
- $label = $r->country;
- $label = $r->state.", $label" if $r->state;
- $label = $r->county." county, $label" if $r->county;
- $label = $r->city. ", $label" if $r->city && $cgi->param('show_cities');
- $label = "$label (". $r->taxclass. ")"
- if $r->taxclass
- && $cgi->param('show_taxclasses')
- && ! $opt{'no_taxclass'};
- $label = $r->taxname. " ($label)" if $r->taxname;
- }
- return $label;
-}
-
-#my %count_taxname = (); #cache
-#sub count_taxname {
-# my $taxname = shift;
-# return $count_taxname{$taxname} if exists $count_taxname{$taxname};
-# my $sql = 'SELECT COUNT(*) FROM cust_main_county WHERE taxname = ?';
-# my $sth = dbh->prepare($sql) or die dbh->errstr;
-# $sth->execute( $taxname )
-# or die "Unexpected error executing statement $sql: ". $sth->errstr;
-# $count_taxname{$taxname} = $sth->fetchrow_arrayref->[0];
-#}
-
-#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up
-#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;
- $sth->fetchrow_arrayref->[0] || 0;
-}
+my $money_sprintf_nonzero = sub {
+ $_[0] == 0 ? '' : &$money_sprintf($_[0])
+};
my $dateagentlink = "begin=$beginning;end=$ending";
$dateagentlink .= ';agentnum='. $cgi->param('agentnum')
if length($agentname);
my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink";
my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink";
-my $creditlink = $p. "search/cust_credit_bill_pkg.html?$dateagentlink";
+my $creditlink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;credit=1";
</%init>
diff --git a/httemplate/search/sqlradius_usage.html b/httemplate/search/sqlradius_usage.html
new file mode 100644
index 000000000..29ef4c0e8
--- /dev/null
+++ b/httemplate/search/sqlradius_usage.html
@@ -0,0 +1,201 @@
+% if ( @include_agents ) {
+% # jumbo report
+<& /elements/header.html, $title &>
+% foreach my $agent ( @include_agents ) {
+% $cgi->param('agentnum', $agent->agentnum); #for download links
+<DIV WIDTH="100%" STYLE="page-break-after: always">
+<FONT SIZE=6><% $agent->agent %></FONT><BR><BR>
+ <& sqlradius_usage.html,
+ export => $export,
+ agentnum => $agent->agentnum,
+ nohtmlheader => 1,
+ usage_by_username => \%usage_by_username,
+ download_label => 'Download this section',
+ &>
+</DIV>
+<BR><BR>
+% }
+<& /elements/footer.html &>
+% } else {
+<& elements/search.html,
+ 'title' => $title,
+ 'name' => 'services',
+ 'query' => $sql_query,
+ 'count_query' => $sql_query->{'count_query'},
+ 'header' => [ #FS::UI::Web::cust_header(),
+ '#',
+ 'Customer',
+ 'Package',
+ @svc_header,
+ 'Upload (GB)',
+ 'Download (GB)',
+ 'Total (GB)',
+ ],
+ 'footer' => \@footer,
+ 'fields' => [ #\&FS::UI::Web::cust_fields,
+ 'display_custnum',
+ 'name',
+ 'pkg',
+ @svc_fields,
+ @svc_usage,
+ ],
+ 'links' => [ #( map { $_ ne 'Cust. Status' ? $link_cust : '' }
+ # FS::UI::Web::cust_header() ),
+ $link_cust,
+ $link_cust,
+ '', #package
+ ( map { $link_svc } @svc_header ),
+ '',
+ '',
+ '',
+ ],
+ 'align' => #FS::UI::Web::cust_aligns() .
+ 'rlc' . ('l' x scalar(@svc_header)) . 'rrr' ,
+ 'nohtmlheader' => ($opt{'nohtmlheader'} || 0),
+ 'download_label' => $opt{'download_label'},
+&>
+% }
+<%init>
+
+my %opt = @_;
+
+die "access denied" unless
+ $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $title = 'Data Usage Report - ';
+my $agentnum;
+my @include_agents;
+
+if ( $opt{'agentnum'} ) {
+ $agentnum = $opt{'agentnum'};
+} elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agentnum = $1;
+}
+
+if ( $agentnum ) {
+ my $agent = FS::agent->by_key($agentnum);
+ $title = $agent->agent." $title";
+} else {
+ @include_agents = qsearch('agent', {});
+}
+
+# usage query params
+my( $beginning, $ending ) = FS::UI::Web::parse_beginning_ending($cgi);
+
+if ( $beginning ) {
+ $title .= time2str('%h %o %Y ', $beginning);
+}
+$title .= 'through ';
+if ( $ending == 4294967295 ) {
+ $title .= 'now';
+} else {
+ $title .= time2str('%h %o %Y', $ending);
+}
+
+my $export;
+my %usage_by_username;
+if ( exists($opt{usage_by_username}) ) {
+ # There's no agent separation in the radacct data. So in the jumbo report
+ # do this procedure once, and pass the hash into all the per-agent sections.
+ %usage_by_username = %{ $opt{usage_by_username} };
+ $export = $opt{export};
+} else {
+
+ $cgi->param('exportnum') =~ /^(\d+)$/
+ or die "illegal export: '".$cgi->param('exportnum')."'";
+ $export = FS::part_export->by_key($1)
+ or die "exportnum $1 not found";
+ $export->exporttype =~ /sqlradius/
+ or die "exportnum ".$export->exportnum." is type ".$export->exporttype.
+ ", not sqlradius";
+
+ my $usage = $export->usage_sessions( {
+ stoptime_start => $beginning,
+ stoptime_end => $ending,
+ summarize => 1
+ } );
+ # arrayref of hashrefs of
+ # (username, acctsessiontime, acctinputoctets, acctoutputoctets)
+ # (XXX needs to include 'realm' for sqlradius_withdomain)
+ # rearrange to be indexed by username.
+
+ foreach (@$usage) {
+ my $username = $_->{'username'};
+ my @row = (
+ $_->{'acctinputoctets'},
+ $_->{'acctoutputoctets'},
+ $_->{'acctinputoctets'} + $_->{'acctoutputoctets'}
+ );
+ $usage_by_username{$username} = \@row;
+ }
+}
+
+#warn Dumper(\%usage_by_username);
+my @total_usage = (0, 0, 0, 0); # session time, input, output, input + output
+my @svc_usage = map {
+ my $i = $_;
+ sub {
+ my $username = $export->export_username(shift);
+ return '' if !exists($usage_by_username{$username});
+ my $value = $usage_by_username{ $username }->[$i];
+ $total_usage[$i] += $value;
+ # for now, always show in GB, rounded to 3 digits
+ bytes_to_gb($value);
+ }
+} (0,1,2);
+
+# set up svcdb-specific stuff
+my $export_username = sub {
+ $export->export_username(shift); # countrycode + phone, formatted MAC, etc.
+};
+
+my %svc_header = (
+ svc_acct => [ 'Username' ],
+ svc_broadband => [ 'MAC address', 'IP address' ],
+# svc_phone => [ 'Phone' ], #not yet supported, no search method
+ # (not sure input/output octets is relevant)
+);
+my %svc_fields = (
+ svc_acct => [ $export_username ],
+ svc_broadband => [ $export_username, 'ip_addr' ],
+# svc_phone => [ $export_username ],
+);
+
+# what kind of service we're operating on
+my $svcdb = FS::part_export::export_info()->{$export->exporttype}->{'svc'};
+my $class = "FS::$svcdb";
+my @svc_header = @{ $svc_header{$svcdb} };
+my @svc_fields = @{ $svc_fields{$svcdb} };
+
+# svc_x search params
+my %search_hash = ( 'agentnum' => $agentnum,
+ 'exportnum' => $export->exportnum );
+
+my $sql_query = $class->search(\%search_hash);
+$sql_query->{'select'} .= ', part_pkg.pkg';
+$sql_query->{'addl_from'} .= ' LEFT JOIN part_pkg USING (pkgpart)';
+
+my $link_svc = [ $p.'view/cust_svc.cgi?', 'svcnum' ];
+
+my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ];
+
+# columns between the customer name and the usage fields
+my $skip_cols = 1 + scalar(@svc_header);
+
+my @footer = (
+ '',
+ FS::Record->scalar_sql($sql_query->{count_query}) . ' services',
+ ('') x $skip_cols,
+ map {
+ my $i = $_;
+ sub { # defer this until the rows have been processed
+ bytes_to_gb($total_usage[$i])
+ }
+ } (0,1,2)
+);
+
+sub bytes_to_gb {
+ $_[0] ? sprintf('%.3f', $_[0] / (1024*1024*1024.0)) : '';
+}
+
+</%init>
diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html
new file mode 100644
index 000000000..02d514cbe
--- /dev/null
+++ b/httemplate/search/unearned_detail.html
@@ -0,0 +1,257 @@
+<& elements/search.html,
+ 'title' => emt("Unearned revenue - ".ucfirst($unearned_mode)) . ' (' .
+ time2str('%b %d %Y', $unearned) . ')',
+ 'name' => emt('line items'),
+ 'query' => $query,
+ 'count_query' => $count_query,
+ 'count_addl' => [ $money_char. '%.2f total',
+ $money_char. '%.2f unearned revenue'
+ ],
+ 'header' => [ map( {emt $_}
+ 'Description',
+ 'Unearned', # depends on mode
+ 'Recurring charge', #recur - usage
+ 'Owed', #recur - usage - credits - payments
+ 'Paid', #payments
+ 'Payment date', #of last payment
+ 'Credit date', #of last credit
+ 'Charge start',
+ 'Charge end',
+ 'Invoice',
+ 'Date'
+ ),
+ FS::UI::Web::cust_header(),
+ ],
+ 'fields' => [
+ #Description
+ sub { $_[0]->pkgnum > 0
+ ? $_[0]->get('pkg') # possibly use override.pkg
+ : $_[0]->get('itemdesc') # but i think this correct
+ },
+ #Unearned
+ money_sub('unearned_revenue'),
+ #Recurring charge
+ money_sub('recur_no_usage'),
+ #Owed
+ money_sub('owed_no_usage'),
+ #Paid
+ money_sub('paid_no_usage'),
+ #Payment date
+ date_sub('last_pay'),
+ #Credit date
+ date_sub('last_credit'),
+ #Charge start
+ date_sub('sdate'),
+ #Charge end, minus most of a day
+ date_sub('before_edate'),
+ #Invoice
+ 'invnum',
+ #Invoice date
+ date_sub('_date'),
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'sort_fields' => [
+ 'pkg',
+ # SQL expressions work as sort keys...
+ 'unearned_revenue',
+ 'recur_no_usage',
+ 'owed_no_usage',
+ 'paid_no_usage',
+ 'last_pay',
+ 'last_credit',
+ 'sdate',
+ 'edate',
+ 'invnum',
+ '_date',
+ ],
+ 'links' => [
+ ('' x 9),
+ $ilink,
+ $ilink,
+ ( map { $_ ne 'Cust. Status' ? $clink : '' }
+ FS::UI::Web::cust_header()
+ ),
+ ],
+ 'align' => 'lrrcrccrc'. FS::UI::Web::cust_aligns(),
+ 'color' => [
+ ('' x 11),
+ FS::UI::Web::cust_colors(),
+ ],
+ 'style' => [
+ ('' x 11),
+ FS::UI::Web::cust_styles(),
+ ],
+&>
+<%init>
+
+# Separated from cust_bill_pkg.cgi to simplify things.
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my $conf = new FS::Conf;
+
+my $unearned = '';
+my $unearned_mode = '';
+my $unearned_base = '';
+my $unearned_sql = '';
+
+my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' );
+my ($join_cust, $join_pkg ) = ('', '');
+
+#here is the agent virtualization
+my $agentnums_sql =
+ $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+
+my @where = ( $agentnums_sql );
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+
+if ( $cgi->param('status') =~ /^([a-z]+)$/ ) {
+ push @where, FS::cust_main->cust_status_sql . " = '$1'";
+}
+
+push @where, "cust_bill._date >= $beginning",
+ "cust_bill._date <= $ending";
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ push @where, "cust_main.agentnum = $1";
+}
+
+# no pkgclass, no taxclass, no tax location...
+
+# unearned revenue mode
+$cgi->param('date') =~ /^(\d+)$/
+ or die "date required";
+
+$unearned = $1;
+$unearned_mode = $cgi->param('mode');
+
+push @where, "cust_bill_pkg.sdate < $unearned",
+ "cust_bill_pkg.edate > $unearned",
+ "cust_bill_pkg.recur != 0",
+ "part_pkg.freq != '0'";
+
+if ( !$cgi->param('include_monthly') ) {
+ push @where,
+ "part_pkg.freq != '1'",
+ "part_pkg.freq NOT LIKE '%h'",
+ "part_pkg.freq NOT LIKE '%d'",
+ "part_pkg.freq NOT LIKE '%w'";
+}
+
+my @opt = (
+ $unearned, #before this date
+ '', #after this date
+ setuprecur => 'recur',
+ no_usage => 1
+);
+
+my $charged = FS::cust_bill_pkg->charged_sql(@opt);
+push @select, "($charged) AS recur_no_usage";
+
+my $owed_sql = FS::cust_bill_pkg->owed_sql(@opt);
+push @select, "($owed_sql) AS owed_no_usage";
+
+my $paid_sql = FS::cust_bill_pkg->paid_sql(@opt);
+push @select, "$paid_sql AS paid_no_usage";
+
+if ( $unearned_mode eq 'paid' ) {
+ # then use the amount paid, minus usage charges
+ $unearned_base = $paid_sql;
+}
+else {
+ # use the amount billed, minus usage charges and credits
+ $unearned_base = "( $charged - " .
+ FS::cust_bill_pkg->credited_sql(@opt) . ' )';
+}
+# whatever we're using as the base, only show rows where it's positive
+push @where, "$unearned_base > 0";
+
+my $edate_zero = midnight_sql('edate');
+my $sdate_zero = midnight_sql('sdate');
+# $unearned is one second before midnight on the date requested for the report.
+
+# suppress partial days for more accounting-like behavior
+my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )";
+
+my $remaining = "GREATEST(
+ CAST( ($edate_zero - $unearned) / 86400.0 AS DECIMAL(10,0) ),
+ 0)";
+my $fraction = "$remaining / $period";
+
+$unearned_sql = "CAST( $unearned_base * $fraction AS DECIMAL(10,2) )";
+push @select, "$unearned_sql AS unearned_revenue";
+
+# last payment/credit date
+my %t = (pay => 'cust_bill_pay', credit => 'cust_credit_bill');
+foreach my $x (qw(pay credit)) {
+ my $table = $t{$x};
+ my $link = $table.'_pkg';
+ my $pkey = dbdef->table($table)->primary_key;
+ my $last_date_sql = "SELECT MAX(_date)
+ FROM $table JOIN $link USING ($pkey)
+ WHERE $link.billpkgnum = cust_bill_pkg.billpkgnum
+ AND $table._date <= $unearned";
+ push @select, "($last_date_sql) AS last_$x";
+}
+
+push @select, '(edate - 82799) AS before_edate';
+
+#no itemdesc
+#no tax report group kludge
+#no tax exemption
+#usage always excluded
+
+# always 'nottax', not 'istax'
+$join_cust = ' JOIN cust_bill USING ( invnum )
+ LEFT JOIN cust_main USING ( custnum ) ';
+
+$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum )
+ LEFT JOIN part_pkg USING ( pkgpart )
+ LEFT JOIN part_pkg AS override
+ ON pkgpart_override = override.pkgpart ';
+
+my $where = ' WHERE '. join(' AND ', @where);
+
+my $count_query = "SELECT COUNT(DISTINCT billpkgnum),
+ SUM( $unearned_base ), SUM( $unearned_sql )
+ FROM cust_bill_pkg $join_cust $join_pkg $where";
+
+push @select, 'part_pkg.pkg',
+ 'part_pkg.freq',
+ 'cust_main.custnum',
+ FS::UI::Web::cust_sql_fields();
+
+my $query = {
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => "$join_cust $join_pkg",
+ 'hashref' => {},
+ 'select' => join(",\n", @select ),
+ 'extra_sql' => $where,
+ 'order_by' => 'ORDER BY cust_bill._date, billpkgnum',
+};
+
+my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+my $money_char;
+
+sub money_sub {
+ $conf ||= new FS::Conf;
+ $money_char ||= $conf->config('money_char') || '$';
+ my $field = shift;
+ sub {
+ $money_char . sprintf('%.2f', $_[0]->get($field));
+ };
+}
+
+sub date_sub {
+ my $field = shift;
+ sub {
+ my $value = $_[0]->get($field);
+ $value ? time2str('%b %d %Y', $value) : '';
+ };
+};
+
+</%init>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
index a8b4ac15c..95ce60b1d 100755
--- a/httemplate/view/cust_bill.cgi
+++ b/httemplate/view/cust_bill.cgi
@@ -166,8 +166,6 @@ die "Invoice #$invnum not found!" unless $cust_bill;
my $custnum = $cust_bill->custnum;
my $display_custnum = $cust_bill->cust_main->display_custnum;
-#my $printed = $cust_bill->printed;
-
my $link = "invnum=$invnum";
$link .= ';template='. uri_escape($template) if $template;
$link .= ';notice_name='. $notice_name if $notice_name;
diff --git a/httemplate/view/cust_bill_void.html b/httemplate/view/cust_bill_void.html
new file mode 100755
index 000000000..2c526747b
--- /dev/null
+++ b/httemplate/view/cust_bill_void.html
@@ -0,0 +1,79 @@
+<& /elements/header.html, mt('Voided Invoice'), menubar(
+ emt("View this customer (#[_1])",$display_custnum) => "${p}view/cust_main.cgi?$custnum",
+) &>
+
+<SCRIPT TYPE="text/javascript">
+function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+}
+</SCRIPT>
+<% areyousure_link("${p}misc/unvoid-cust_bill_void.html?invnum=". $cust_bill_void->invnum,
+ emt('Are you sure you want to unvoid this invoice?'),
+ emt('Unvoid this invoice'), #tooltip
+ emt('Unvoid this invoice') #link
+ )
+%>
+<BR><BR>
+
+% #voided PDFs?
+% #if ( $conf->exists('invoice_latex') ) {
+%#
+%# <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>"><% mt('View typeset invoice PDF') |h %></A>
+%# <BR><BR>
+% #}
+
+%#something very big and obvious showing its voided...
+<DIV STYLE="color:#FF0000; font-size:1000%; font-weight:bold; z-index:100;
+ position: absolute; top: 300px; left: 130px;
+ zoom: 1; filter: alpha(opacity=25); opacity: 0.25;
+">VOID</DIV>
+
+% if ( $conf->exists('invoice_html') ) {
+ <% join('', $cust_bill_void->print_html(\%opt) ) %>
+% } else {
+ <PRE><% join('', $cust_bill_void->print_text(\%opt) ) %></PRE>
+% }
+
+<& /elements/footer.html &>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices');
+
+my $invnum;
+my($query) = $cgi->keywords;
+if ( $query =~ /^(\d+)$/ ) {
+ $invnum = $1;
+} else {
+ $invnum = $cgi->param('invnum');
+}
+
+my $conf = new FS::Conf;
+
+my %opt = (
+ 'unsquelch_cdr' => $conf->exists('voip-cdr_email'),
+);
+
+my $cust_bill_void = qsearchs({
+ 'select' => 'cust_bill_void.*',
+ 'table' => 'cust_bill_void',
+ #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ #'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Voided invoice #$invnum not found!" unless $cust_bill_void;
+
+my $custnum = $cust_bill_void->custnum;
+my $display_custnum = $cust_bill_void->cust_main->display_custnum;
+
+#my $link = "invnum=$invnum";
+
+sub areyousure_link {
+ my ($url,$msg,$title,$label) = (shift,shift,shift,shift);
+ '<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'">'.$label.'</A>';
+}
+
+</%init>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index 9ce55b0b9..ec3191971 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -46,10 +46,39 @@ function areyousure(href, message) {
<A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>"><% mt('Edit this customer') |h %></A> |
% }
-% if ( $curuser->access_right('Cancel customer')
-% && $cust_main->ncancelled_pkgs
+% if ( $curuser->access_right('Suspend customer')
+% && scalar($cust_main->unsuspended_pkgs)
% ) {
+ <& /elements/popup_link-cust_main.html,
+ { 'action' => $p. 'misc/suspend_cust.html',
+ 'label' => emt('Suspend this customer'),
+ 'actionlabel' => emt('Confirm Suspension'),
+ 'color' => '#ff9900',
+ 'cust_main' => $cust_main,
+ 'width' => 616, #make room for reasons
+ 'height' => 366,
+ }
+ &> |
+% }
+% if ( $curuser->access_right('Unsuspend customer')
+% && scalar($cust_main->suspended_pkgs)
+% ) {
+ <& /elements/popup_link-cust_main.html,
+ { 'action' => $p. 'misc/unsuspend_cust.html',
+ 'label' => emt('Unsuspend this customer'),
+ 'actionlabel' => emt('Confirm Unsuspension'),
+ #'color' => '#ff9900',
+ 'cust_main' => $cust_main,
+ #'width' => 616, #make room for reasons
+ #'height' => 366,
+ }
+ &> |
+% }
+
+% if ( $curuser->access_right('Cancel customer')
+% && scalar($cust_main->ncancelled_pkgs)
+% ) {
<& /elements/popup_link-cust_main.html,
{ 'action' => $p. 'misc/cancel_cust.html',
'label' => emt('Cancel this customer'),
@@ -60,11 +89,9 @@ function areyousure(href, message) {
'height' => 366,
}
&> |
-
% }
% if ( $curuser->access_right('Merge customer') ) {
-
<& /elements/popup_link-cust_main.html,
{ 'action' => $p. 'misc/merge_cust.html',
'label' => emt('Merge this customer'),
@@ -74,7 +101,6 @@ function areyousure(href, message) {
'height' => 192,
}
&> |
-
% }
% if ( $conf->exists('deletecustomers')
@@ -277,7 +303,13 @@ function areyousure(href, message) {
% }
% if ( $view eq 'custom' ) {
+% if ( $conf->config('cust_main-custom_link') ) {
<& cust_main/custom.html, $cust_main &>
+% } elsif ( $conf->config('cust_main-custom_content') ) {
+ <& cust_main/custom_content.html, $cust_main &>
+% #} else {
+% # warn "custom view without cust_main-custom_link or -custom_content?";
+% }
% }
</DIV>
@@ -326,7 +358,8 @@ $views{emt('Payment History')} = 'payment_history'
$views{emt('Change History')} = 'change_history'
if $curuser->access_right('View customer history');
$views{$conf->config('cust_main-custom_title') || emt('Custom')} = 'custom'
- if $conf->config('cust_main-custom_link');
+ if $conf->config('cust_main-custom_link')
+ || $conf->config('cust_main-custom_content');
$views{emt('Jumbo')} = 'jumbo';
my %viewname = reverse %views;
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index 522c6db86..5c46803d2 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -23,6 +23,14 @@
<TD BGCOLOR="#ffffff"><B><% $balance %></B></TD>
</TR>
+% if ( $conf->exists('cust_main-select-prorate_day') ) {
+<TR>
+ <TD ALIGN="right"><% mt('Prorate day of month') |h %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->prorate_day %>
+ </TD>
+</TR>
+% }
+
% if ( $conf->exists('cust_main-select-billday')
% && ($cust_main->payby eq 'CARD' || $cust_main->payby eq 'CHEK') ) {
<TR>
@@ -146,7 +154,7 @@
% if ( $cust_main->payinfo ) {
<TR>
- <TD ALIGN="right"<% mt('P.O.') |h %></TD>
+ <TD ALIGN="right"><% mt('P.O.') |h %></TD>
<TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
</TR>
% }
@@ -189,15 +197,25 @@
% my $no = emt('no');
% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
-<TR>
- <TD ALIGN="right"><% mt('Tax exempt') |h %><% @exempt_groups ? ' ('.emt('all taxes').')' : '' %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->tax ? $yes : $no %></TD>
-</TR>
+
+% unless ( $conf->exists('cust_class-tax_exempt')
+% || $conf->exists('tax-cust_exempt-groups-require_individual_nums')
+% )
+% {
+ <TR>
+ <TD ALIGN="right"><% mt('Tax exempt') |h %><% @exempt_groups ? ' ('.emt('all taxes').')' : '' %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax ? $yes : $no %></TD>
+ </TR>
+% }
+
% foreach my $exempt_group ( @exempt_groups ) {
-<TR>
- <TD ALIGN="right"><% mt('Tax exempt') |h %> (<% $exempt_group %> taxes)</TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->tax_exemption($exempt_group) ? $yes : $no %></TD>
-</TR>
+% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group);
+ <TR>
+ <TD ALIGN="right"><% mt('Tax exempt') |h %> (<% $exempt_group %> taxes)</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main_exemption ? $yes : $no %>
+ <% $cust_main_exemption ? $cust_main_exemption->exempt_number : '' |h %>
+ </TD>
+ </TR>
% }
% if ( $conf->exists('enable_taxproducts') ) {
@@ -207,21 +225,27 @@
</TR>
% }
<TR>
- <TD ALIGN="right"><% mt('Postal invoices') |h %></TD>
+ <TD ALIGN="right"><% mt('Postal mail invoices') |h %></TD>
<TD BGCOLOR="#ffffff">
<% ( grep { $_ eq 'POST' } @invoicing_list ) ? $yes : $no %>
</TD>
</TR>
<TR>
- <TD ALIGN="right"><% mt('FAX invoices') |h %></TD>
+ <TD ALIGN="right"><% mt('Fax invoices') |h %></TD>
<TD BGCOLOR="#ffffff">
<% ( grep { $_ eq 'FAX' } @invoicing_list ) ? $yes : $no %>
</TD>
</TR>
-% unless ( $conf->exists('cust-email-high-visibility')) {
<TR>
<TD ALIGN="right"><% mt('Email invoices') |h %></TD>
<TD BGCOLOR="#ffffff">
+ <% $cust_main->invoice_noemail ? $no : $yes %>
+ </TD>
+</TR>
+% unless ( $conf->exists('cust-email-high-visibility')) {
+<TR>
+ <TD ALIGN="right"><% mt('Email address(es)') |h %></TD>
+ <TD BGCOLOR="#ffffff">
<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
</TD>
</TR>
diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html
index b3e52b556..d65af669a 100644
--- a/httemplate/view/cust_main/contacts.html
+++ b/httemplate/view/cust_main/contacts.html
@@ -1,122 +1,133 @@
-% my %which = (
-% '' => emt('Billing'),
-% 'ship_' => emt('Service'),
-% );
-% foreach my $which ( '', 'ship_' ) {
-% my $pre = $cust_main->get("${which}last") ? $which : '';
-
-<FONT CLASS="fsinnerbox-title"><% $which{$which} %> <% mt('address') |h %></FONT>
+% my %addr_label = ('bill' => 'Billing address', 'ship' => 'Service address');
+
+%# Locations (possibly break this out)
+% my @which = ('bill', 'ship');
+% while (@which) {
+% my $this = shift @which;
+% my $method = $this.'_location';
+% my $location = $cust_main->$method;
+<FONT CLASS="fsinnerbox-title"><% mt( $addr_label{$this} ) |h %>
+% if ( $this eq 'ship' and
+% $cust_main->bill_locationnum == $cust_main->ship_locationnum )
+% {
+ (<% mt('same as billing') %>)
+% }
+</FONT>
<TABLE CLASS="fsinnerbox">
-<TR>
- <TD ALIGN="right"><% mt('Contact name') |h %></TD>
- <TD COLSPAN=5 BGCOLOR="#ffffff">
- <% $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") |h %>
- </TD>
-% if ( $which eq '' && $conf->exists('show_ss') ) {
- <TD ALIGN="right"><% mt('SS#') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->masked('ss') || '&nbsp' %></TD>
-% }
-</TR>
-% if ( $conf->exists('cust-email-high-visibility') && $which eq '') {
+% if ( $this eq 'bill' ) {
+% #billing contact fields
+ <TR>
+ <TD ALIGN="right"><% mt('Contact name') |h %></TD>
+ <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->contact |h %></TD>
+% if ( $conf->exists('show_ss') ) {
+ <TD ALIGN="right"><% mt('SS#') |h %></TD>
+ <TD BGCOLOR="#ffffff"><% $conf->exists('unmask_ss')
+ ? $cust_main->ss
+ : $cust_main->masked('ss') || '&nbsp;' %></TD>
+% }
+ </TR>
+% if ( $conf->exists('cust-email-high-visibility') ) {
<TR>
- <TD ALIGN="right"><% mt('Email invoices') |h %></TD>
+ <TD ALIGN="right"><% mt('Email address(es)') |h %></TD>
<TD BGCOLOR="#ffff00">
- <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %>
+ <% $cust_main->invoicing_list_emailonly_scalar || $no %>
</TD>
</TR>
-% }
-
-% if ( $cust_main->get("${pre}company") ) {
+% }
+% if ( $cust_main->company ) {
<TR>
<TD ALIGN="right"><% mt('Company') |h %></TD>
- <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") |h %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->company |h %></TD>
</TR>
-% }
-
+% }
+% } # if $this eq 'bill'
+% # now the actual address
<TR>
<TD ALIGN="right"><% mt('Address') |h %></TD>
- <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") |h %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $location->address1 |h %></TD>
</TR>
-% if ( $cust_main->get("${pre}address2") ) {
-% my $address2_label =
-% ( $conf->exists('cust_main-require_address2')
-% && ! ( $pre xor $cust_main->has_ship_address )
-% )
-% ? emt('Unit #')
-% : ' ';
+% if ( $location->get('address2') ) {
+% my $address2_label = $conf->exists('cust_main-require_address2')
+% ? emt('Unit #')
+% : ' ';
- <TR>
- <TD ALIGN="right"><% $address2_label %></TD>
- <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") |h %></TD>
- </TR>
+<TR>
+ <TD ALIGN="right"><% $address2_label %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $location->address2 |h %></TD>
+</TR>
% }
<TR>
<TD ALIGN="right"><% mt('City') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") |h %></TD>
-% if ( $cust_main->get("${pre}county") ) {
+ <TD BGCOLOR="#ffffff"><% $location->city |h %></TD>
+% if ( $location->county ) {
<TD ALIGN="right"><% mt('County') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}county") |h %></TD>
+ <TD BGCOLOR="#ffffff"><% $location->county |h %></TD>
% }
<TD ALIGN="right"><% mt('State') |h %></TD>
- <TD BGCOLOR="#ffffff"><% state_label( $cust_main->get("${pre}state"), $cust_main->get("${pre}country") ) |h %></TD>
+ <TD BGCOLOR="#ffffff"><% state_label( $location->state, $location->country ) |h %></TD>
<TD ALIGN="right"><% mt('Zip') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}zip") %></TD>
+ <TD BGCOLOR="#ffffff"><% $location->zip %></TD>
</TR>
<TR>
<TD ALIGN="right"><% mt('Country') |h %></TD>
- <TD BGCOLOR="#ffffff"><% code2country( $cust_main->get("${pre}country") ) %></TD>
+ <TD BGCOLOR="#ffffff"><% code2country( $location->country ) %></TD>
</TR>
-% if ( $cust_main->get($pre.'latitude') && $cust_main->get($pre.'longitude') ) {
- <& /elements/tr-coords.html, $cust_main->get($pre.'latitude'),
- $cust_main->get($pre.'longitude'),
+% if ( $location->latitude && $location->longitude ) {
+ <& /elements/tr-coords.html, $location->latitude,
+ $location->longitude,
$cust_main->name_short,
$cust_main->agentnum,
&>
% }
+
+% if ( $this eq 'bill' ) {
+% # billing contact phone numbers
+% foreach my $phone (qw(daytime night mobile)) {
+% next if !$cust_main->get($phone);
+<TR>
+ <TD ALIGN="right"><% $phone_label{$phone} %></TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <& /elements/phonenumber.html,
+ $cust_main->get($phone),
+ callable => 1,
+ calling_list_exempt => $cust_main->calling_list_exempt,
+ &>
+ </TD>
+</TR>
-% foreach my $phone (grep $cust_main->get($pre.$_), qw( daytime night mobile )){
-
- <TR>
- <TD ALIGN="right"><% $phone_label{$phone} %></TD>
- <TD COLSPAN=3 BGCOLOR="#ffffff">
- <& /elements/phonenumber.html,
- $cust_main->get($pre.$phone),
- 'callable'=>1,
- 'calling_list_exempt'=>$cust_main->calling_list_exempt,
- &>
- </TD>
- </TR>
-
-% }
+% } #foreach $phone
+% if ( $cust_main->get('fax') ) {
-% if ( $cust_main->get("${pre}fax") ) {
<TR>
<TD ALIGN="right"><% mt('Fax') |h %></TD>
<TD COLSPAN=3 BGCOLOR="#ffffff">
- <% $cust_main->get("${pre}fax") || '&nbsp' %>
+ <% $cust_main->get('fax') || '&nbsp;' %>
</TD>
</TR>
-% }
-% if ( $which eq '' && $conf->exists('show_stateid') ) {
- <TR>
+% }
+%
+% if ( $conf->exists('show_stateid') ) {
+
+<TR>
<TD ALIGN="right"><% $stateid_label %></TD>
<TD BGCOLOR="#ffffff"><% $cust_main->masked('stateid') || '&nbsp' %></TD>
<TD ALIGN="right"><% $stateid_state_label %></TD>
<TD BGCOLOR="#ffffff"><% $cust_main->stateid_state || '&nbsp' %></TD>
</TR>
-% }
+% }
+% } #if $this eq 'bill'
</TABLE>
-% if ( $which ne 'ship_' ) {
+% if ( @which ) {
<BR>
% }
-% }
+% } #while @which
<%once>
my %phone_label = (
@@ -147,7 +158,7 @@ my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_st
</%once>
<%init>
-my( $cust_main ) = @_;
+my $cust_main = shift;
my $conf = new FS::Conf;
my @invoicing_list = $cust_main->invoicing_list;
my $no = emt('no');
diff --git a/httemplate/view/cust_main/custom_content.html b/httemplate/view/cust_main/custom_content.html
new file mode 100644
index 000000000..dd3d1f0d4
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content.html
@@ -0,0 +1,31 @@
+% foreach my $item (@content) {
+%
+% if ( $item =~ /^\s*$/ ) {
+ <BR>
+% next;
+% }
+%
+% if ( $items{$item} ) {
+ <& "custom_content/$item.html", $cust_main &>
+% } else {
+ Unknown item <% $item |h %><BR>
+% }
+% }
+<%init>
+
+my($cust_main) = @_;
+
+my $conf = new FS::Conf;
+
+my @content = $conf->config('cust_main-custom_content');
+
+my %items = map { $_=>1 } qw(
+ small_custview
+ birthdate
+ spouse_birthdate
+ svc_acct
+ svc_phone
+ svc_hardware
+);
+
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/birthdate.html b/httemplate/view/cust_main/custom_content/birthdate.html
new file mode 100644
index 000000000..1f16963af
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/birthdate.html
@@ -0,0 +1,15 @@
+<TABLE CLASS="fsinnerbox">
+ <& /elements/tr-td-label.html, 'label' => mt('Date of Birth') &>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+</TR>
+</TABLE>
+<%init>
+my($cust_main) = @_;
+my $conf = new FS::Conf;
+my $date_format = ($conf->config('date_format') || "%m/%d/%Y");
+my $dt = $cust_main->birthdate ne ''
+ ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate,
+ 'time_zone' =>'floating',
+ )
+ : '';
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/small_custview.html b/httemplate/view/cust_main/custom_content/small_custview.html
new file mode 100644
index 000000000..7c724c78f
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/small_custview.html
@@ -0,0 +1,4 @@
+<& /elements/small_custview.html, $cust_main &>
+<%init>
+my($cust_main) = @_;
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/spouse_birthdate.html b/httemplate/view/cust_main/custom_content/spouse_birthdate.html
new file mode 100644
index 000000000..c78fd26a5
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/spouse_birthdate.html
@@ -0,0 +1,15 @@
+<TABLE CLASS="fsinnerbox">
+ <& /elements/tr-td-label.html, 'label' => mt('Spouse Date of Birth') &>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+</TR>
+</TABLE>
+<%init>
+my($cust_main) = @_;
+my $conf = new FS::Conf;
+my $date_format = ($conf->config('date_format') || "%m/%d/%Y");
+my $dt = $cust_main->spouse_birthdate ne ''
+ ? DateTime->from_epoch( 'epoch' => $cust_main->spouse_birthdate,
+ 'time_zone' =>'floating',
+ )
+ : '';
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/svc_Common.html b/httemplate/view/cust_main/custom_content/svc_Common.html
new file mode 100644
index 000000000..bddb8bf16
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/svc_Common.html
@@ -0,0 +1,40 @@
+% foreach my $cust_svc (@cust_svc) {
+% my $svc_x = $cust_svc->svc_x;
+<TABLE CLASS="fsinnerbox">
+ <TR>
+ <TH COLSPAN=2><% $cust_svc->part_svc->svc |h %></TH>
+ </TR>
+% foreach my $field ( grep $svc_x->$_(), @{ $opt{fields} } ) {
+ <& /elements/tr-td-label.html, 'label' => $labels{$field} &>
+ <TD BGCOLOR="#ffffff"><% $svc_x->$field() |h %></TD>
+ </TR>
+% }
+</TABLE>
+% }
+<%init>
+my($cust_main, %opt) = @_;
+
+my $table = $opt{table};
+my @cust_svc = ();
+foreach my $cust_pkg (
+ grep $_->num_cust_svc( 'svcdb'=>$table ),
+ $cust_main->all_pkgs
+) {
+ my @wtf = $cust_pkg->cust_svc( 'svcdb'=>$table );
+ push @cust_svc, $cust_pkg->cust_svc( 'svcdb'=>$table );
+}
+
+my %labels;
+if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
+# $opt{'name'} = "FS::$table"->table_info->{'name'};
+
+ my $fields = "FS::$table"->table_info->{'fields'};
+ %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/svc_acct.html b/httemplate/view/cust_main/custom_content/svc_acct.html
new file mode 100644
index 000000000..49b97986d
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/svc_acct.html
@@ -0,0 +1,7 @@
+<& svc_Common.html, $cust_main,
+ 'table' => 'svc_acct',
+ 'fields' => [qw( username _password )],
+&>
+<%init>
+my($cust_main) = @_;
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/svc_hardware.html b/httemplate/view/cust_main/custom_content/svc_hardware.html
new file mode 100644
index 000000000..f5d53a251
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/svc_hardware.html
@@ -0,0 +1,7 @@
+<& svc_Common.html, $cust_main,
+ 'table' => 'svc_hardware',
+ 'fields' => [qw( ip_addr hw_addr serial )],
+&>
+<%init>
+my($cust_main) = @_;
+</%init>
diff --git a/httemplate/view/cust_main/custom_content/svc_phone.html b/httemplate/view/cust_main/custom_content/svc_phone.html
new file mode 100644
index 000000000..46ec476bc
--- /dev/null
+++ b/httemplate/view/cust_main/custom_content/svc_phone.html
@@ -0,0 +1,7 @@
+<& svc_Common.html, $cust_main,
+ 'table' => 'svc_phone',
+ 'fields' => [qw( phonenum )],
+&>
+<%init>
+my($cust_main) = @_;
+</%init>
diff --git a/httemplate/view/cust_main/locations.html b/httemplate/view/cust_main/locations.html
index 98c933645..b29d0ce4d 100755
--- a/httemplate/view/cust_main/locations.html
+++ b/httemplate/view/cust_main/locations.html
@@ -5,12 +5,17 @@ span.loclabel {
background-color: #cccccc;
border: 1px solid black
}
+table.location {
+ width: 100%;
+ padding: 1px;
+ border-spacing: 0px;
+}
</STYLE>
% foreach my $locationnum (@sorted) {
% my $packages = $packages_in{$locationnum};
% my $loc = $locations{$locationnum};
% next if $loc->disabled and scalar(@$packages) == 0;
-<& /elements/table-grid.html &>
+<TABLE CLASS="grid location">
<TR><TH COLSPAN=3 ALIGN="left" VALIGN="bottom"
STYLE="padding-bottom: 0px;
padding-left: 0px;
@@ -18,10 +23,7 @@ STYLE="padding-bottom: 0px;
border-bottom-color: black;
border-bottom-width: 1px;">
<SPAN CLASS="loclabel">
-% if (! $locationnum) {
-<% mt('Default service location:') |h %>
-% }
-% elsif ( $loc->disabled ) {
+% if ( $loc->disabled ) {
<FONT COLOR="#808080"><I>
% }
<% $loc->location_label %></SPAN>
@@ -49,8 +51,7 @@ my %locations = map { $_->locationnum => $_ } qsearch({
'order_by' => 'ORDER BY country, state, city, address1, locationnum',
});
my @sections = keys %locations;
-$locations{''} = $cust_main;
-my %packages_in = map { $_ => [] } ('', @sections);
+my %packages_in = map { $_ => [] } (@sections);
my %active = (); # groups with non-canceled packages
foreach my $cust_pkg ( @$all_packages ) {
@@ -58,10 +59,13 @@ foreach my $cust_pkg ( @$all_packages ) {
push @{ $packages_in{$key} }, $cust_pkg;
$active{$key} = 1 if !$cust_pkg->getfield('cancel');
}
+# prevent disabling these
+$active{$cust_main->ship_locationnum} = 1;
+$active{$cust_main->bill_locationnum} = 1;
my @sorted = (
- '',
- grep ( { $active{$_} } @sections),
+ $cust_main->ship_locationnum,
+ grep ( { $active{$_} && $_ != $cust_main->ship_locationnum } @sections),
grep ( { !$active{$_} } @sections),
);
diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html
index 295328721..263c266e7 100644
--- a/httemplate/view/cust_main/misc.html
+++ b/httemplate/view/cust_main/misc.html
@@ -102,6 +102,26 @@
<TD BGCOLOR="#ffffff"><% $cust_main->signupdate ? time2str($date_format, $cust_main->signupdate) : '' %></TD>
</TR>
+% my $id_country = $conf->config('national_id-country');
+% if ( $id_country ) {
+% if ( $id_country eq 'MY' ) {
+ <TR>
+% my($old, $nric) = ( '', '');
+% if ( $cust_main->national_id =~ /^\d{6}\-\d{2}\-\d{4}$/ ) {
+ <TD ALIGN="right"><% mt('NRIC') |h %></TD>
+% } else { # elsif ( $cust_main->national_id =~ /^\w\d{9}$/ ) {
+ <TD ALIGN="right"><% mt('Old IC/Passport') |h %></TD>
+% #} else {
+% # warn "unknown national_id format";
+%# <TD ALIGN="right"></TD>
+% }
+ <TD BGCOLOR="#ffffff"><% $cust_main->national_id |h %></TD>
+ </TR>
+% } else {
+% warn "unknown national_id-country $id_country";
+% }
+% }
+
% if ( $conf->exists('cust_main-enable_birthdate') ) {
% my $dt = $cust_main->birthdate ne ''
% ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate,
@@ -116,13 +136,41 @@
% }
+% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) {
+% my $dt = $cust_main->spouse_birthdate ne ''
+% ? DateTime->from_epoch( 'epoch' => $cust_main->spouse_birthdate,
+% 'time_zone' =>'floating',
+% )
+% : '';
+
+ <TR>
+ <TD ALIGN="right"><% mt('Spouse Date of Birth') |h %></TD>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+ </TR>
+
+% }
+
+% if ( $conf->exists('cust_main-enable_anniversary_date') ) {
+% my $dt = $cust_main->anniversary_date ne ''
+% ? DateTime->from_epoch( 'epoch' => $cust_main->anniversary_date,
+% 'time_zone' =>'floating',
+% )
+% : '';
+
+ <TR>
+ <TD ALIGN="right"><% mt('Anniversary Date') |h %></TD>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+ </TR>
+
+% }
+
% if ( $conf->exists('cust_main-require_censustract') ) {
<TR>
<TD ALIGN="right">
- <% mt('Census tract ([_1])', $cust_main->censusyear) |h %>
+ <% mt('Census tract ([_1])', $cust_main->ship_location->censusyear) |h %>
</TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->censustract %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->censustract %></TD>
</TR>
% }
@@ -131,7 +179,7 @@
<TR>
<TD ALIGN="right"><% mt('Tax district') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->district %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->district %></TD>
</TR>
% }
diff --git a/httemplate/view/cust_main/order_pkg_link.html b/httemplate/view/cust_main/order_pkg_link.html
deleted file mode 100644
index 7bc5eef31..000000000
--- a/httemplate/view/cust_main/order_pkg_link.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<& /elements/popup_link-cust_main.html,
- 'action' => $p. 'misc/order_pkg.html',
- 'label' => $opt{'label'} || emt('Order new package'),
- 'actionlabel' => emt('Order new package'),
- 'color' => '#333399',
- 'cust_main' => $cust_main,
- 'closetext' => emt('Close'),
- 'width' => 960, #763,
- 'height' => $height,
- %optional,
-&>
-<%init>
-
-my($cust_main, %opt) = @_;
-
-my %optional = map { $_ => $opt{$_} }
- grep $opt{$_},
- qw( lock_pkgpart lock_locationnum qualnum svcpart );
-
-my $height = $opt{'lock_locationnum'} ? 336 : 576;
-
-</%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index e16b2c1d4..7d7930634 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -7,7 +7,7 @@
% if ( $curuser->access_right('Order customer package') ) {
<% $s++ ? ' | ' : '' %>
- <& order_pkg_link.html, $cust_main &>
+ <& /elements/order_pkg_link.html, 'cust_main'=>$cust_main &>
% }
% if ( $curuser->access_right('One-time charge')
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
index 5f458e635..c0a56d0f3 100644
--- a/httemplate/view/cust_main/packages/services.html
+++ b/httemplate/view/cust_main/packages/services.html
@@ -3,7 +3,7 @@
% ###
<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
- <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=2 WIDTH="100%">
<SCRIPT TYPE="text/javascript">
function clearhint_search_cust_svc(obj, str) {
if (obj.value == str) obj.value = '';
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index 28df9da95..e9017745b 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -32,7 +32,20 @@
% }
%
-% } else {
+% if ( $part_pkg->freq ) { #?
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Un-cancel customer package') ) {
+ (&nbsp;<% pkg_uncancel_link($cust_pkg) %>&nbsp;)
+% }
+ <FONT>
+ </TD>
+ </TR>
+% }
+%
+% } else {
%
% if ( $cust_pkg->get('susp') ) { #status: suspended
% my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
@@ -56,9 +69,16 @@
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %>
% }
+ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
<% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
<% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
-% if ( $part_pkg->option('suspend_bill', 1) ) {
+% if ( $cust_pkg->option('suspend_bill', 1)
+% || ( $part_pkg->option('suspend_bill', 1)
+% && ! $cust_pkg->option('no_suspend_bill',1)
+% )
+% )
+% {
<% pkg_status_row_if( $cust_pkg, emt('Next bill'), 'bill', %opt, curuser=>$curuser ) %>
% }
<% pkg_status_row_if( $cust_pkg, emt('Will resume'), 'resume', %opt, curuser=>$curuser ) %>
@@ -99,6 +119,8 @@
)
%>
+ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
<TR>
<TD COLSPAN=<%$colspan%>>
<FONT SIZE=-1>
@@ -118,6 +140,7 @@
<% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
<% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %>
+ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
% }
%
@@ -133,6 +156,8 @@
<% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
% } else {
%
% my $num_cust_svc = $cust_pkg->num_cust_svc;
@@ -166,6 +191,8 @@
<% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %>
+ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %>
+
% }
%
% }
@@ -467,6 +494,16 @@ sub pkg_cancel_link {
)
}
+sub pkg_uncancel_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=uncancel',
+ 'label' => emt('Un-cancel'),
+ 'actionlabel' => emt('Un-cancel'),
+ #'color' => #?
+ 'cust_pkg' => shift,
+ )
+}
+
sub pkg_expire_link {
include( '/elements/popup_link-cust_pkg.html',
'action' => $p. 'misc/cancel_pkg.html?method=expire',
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index c7a7c8024..166addbf4 100644
--- a/httemplate/view/cust_main/payment_history.html
+++ b/httemplate/view/cust_main/payment_history.html
@@ -277,7 +277,9 @@
% ? sprintf("$money_char\%.2f", $item->{'charge'})
% : exists($item->{'charge_nobal'})
% ? sprintf("$money_char\%.2f", $item->{'charge_nobal'})
-% : '';
+% : exists($item->{'void_charge'})
+% ? sprintf("<DEL>$money_char\%.2f</DEL>", $item->{'void_charge'})
+% : '';
%
% my $payment = exists($item->{'payment'})
% ? sprintf("-&nbsp;$money_char\%.2f", $item->{'payment'})
@@ -428,6 +430,15 @@ foreach my $cust_bill ($cust_main->cust_bill) {
$num_cust_bill++;
}
+#voided invoices
+foreach my $cust_bill_void ($cust_main->cust_bill_void) {
+ push @history, {
+ 'date' => $cust_bill_void->_date,
+ 'desc' => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ),
+ 'void_charge' => $cust_bill_void->charged,
+ };
+}
+
#statements
foreach my $cust_statement ($cust_main->cust_statement) {
push @history, {
@@ -491,7 +502,7 @@ foreach my $cust_credit ($cust_main->cust_credit) {
foreach my $cust_refund ($cust_main->cust_refund) {
push @history, {
'date' => $cust_refund->_date,
- 'desc' => include('payment_history/refund.html', $cust_refund),
+ 'desc' => include('payment_history/refund.html', $cust_refund, %opt),
'refund' => $cust_refund->refund,
};
diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html
index 3028f0f69..96a9f5456 100644
--- a/httemplate/view/cust_main/payment_history/invoice.html
+++ b/httemplate/view/cust_main/payment_history/invoice.html
@@ -1,4 +1,4 @@
-<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $under %>
+<% $link %><% $invoice %><% $link ? '</A>' : '' %><% "$void$delete$under" %>
<%init>
my( $cust_bill, %opt ) = @_;
@@ -26,6 +26,18 @@ my $link = $curuser->access_right('View invoices')
? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
: '';
+my $void = '';
+if ( $cust_bill->closed !~ /^Y/i && $curuser->access_right('Void invoices') ) {
+ $void =
+ ' ('. include('/elements/popup_link.html',
+ 'label' => emt('void'),
+ 'action' => "${p}misc/void-cust_bill.html?;invnum=".
+ $cust_bill->invnum,
+ 'actionlabel' => emt('Void Invoice'),
+ ).
+ ')';
+}
+
my $delete = '';
$delete = areyousure_link("${p}misc/delete-cust_bill.html?$invnum",
emt('Are you sure you want to delete this invoice?'),
diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
index d7322a2d6..ff269bfaf 100644
--- a/httemplate/view/cust_main/payment_history/payment.html
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -181,7 +181,7 @@ $void = areyousure_link("${p}misc/void-cust_pay.cgi?".$cust_pay->paynum,
&& $curuser->access_right('Echeck void')
)
|| ( $cust_pay->payby !~ /^(CARD|CHEK)$/
- && $curuser->access_right('Regular void')
+ && $curuser->access_right('Void payments')
)
)
);
diff --git a/httemplate/view/cust_main/payment_history/voided_invoice.html b/httemplate/view/cust_main/payment_history/voided_invoice.html
new file mode 100644
index 000000000..15393cbf5
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/voided_invoice.html
@@ -0,0 +1,57 @@
+<DEL><% $link %><% $invoice %><% $link ? '</A>' : '' %></DEL>
+<I><% mt("voided [_1]", time2str($date_format, $cust_bill_void->void_date) ) |h %>
+% my $void_user = $cust_bill_void->void_access_user;
+% if ($void_user) {
+ by <% $void_user->username %></I>
+% }
+<% "$unvoid$delete$under" %>
+<%init>
+
+my( $cust_bill_void, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $invoice = emt("Invoice #[_1] (Balance [_2])",$cust_bill_void->display_invnum, $cust_bill_void->charged);
+
+my $under = '';
+
+my $invnum = $cust_bill_void->invnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_bill_void.html?$invnum">!
+ : '';
+
+my $unvoid = '';
+$unvoid = areyousure_link("${p}misc/unvoid-cust_bill_void.html?invnum=". $cust_bill_void->invnum,
+ emt('Are you sure you want to unvoid this invoice?'),
+ emt('Unvoid this invoice'),
+ emt('unvoid')
+ )
+ if $cust_bill_void->closed !~ /^Y/ && $curuser->access_right('Unvoid invoices');
+
+my $delete = '';
+$delete = areyousure_link("${p}misc/delete-cust_bill.html?$invnum",
+ emt('Are you sure you want to delete this invoice?'),
+ emt('Delete this invoice from the database completely'),
+ emt('delete')
+ )
+ if ( $opt{'deleteinvoices'} && $curuser->access_right('Delete invoices') );
+
+my $events = '';
+#1.9
+if ( $cust_bill_void->num_cust_event
+ && ( $curuser->access_right('Billing event reports')
+ || $curuser->access_right('View customer billing events')
+ )
+ ) {
+ $under .=
+ qq!<BR><A HREF="${p}search/cust_event.html?invnum=$invnum">( !.
+ emt('View invoice events').' )</A>';
+}
+$under = '<FONT SIZE="-1">'.$under.'</FONT>' if length($under);
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html
index 2f038be41..88b5e0a84 100644
--- a/httemplate/view/cust_main/payment_history/voided_payment.html
+++ b/httemplate/view/cust_main/payment_history/voided_payment.html
@@ -31,6 +31,6 @@ $unvoid = areyousure_link("${p}misc/unvoid-cust_pay_void.cgi?".$cust_pay_void->p
emt('Unvoid this payment from the database') . $unvoidmsg,
emt('unvoid')
)
- if ( $cust_pay_void->closed !~ /^Y/i && $curuser->access_right('Unvoid') );
+ if ( $cust_pay_void->closed !~ /^Y/i && $curuser->access_right('Unvoid payments') );
</%init>
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
index 194e90742..f076fcc92 100644
--- a/httemplate/view/cust_main/tickets.html
+++ b/httemplate/view/cust_main/tickets.html
@@ -1,164 +1,2 @@
-<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline">
-<SCRIPT TYPE="text/javascript">
-function updateTicketLink() {
- var link = document.getElementById('CreateTicketLink');
- var selector = document.getElementById('Queue')
- link.href = "<% $new_base.'?'.
- join(';', map(
- { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"}
- keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value;
-}
-</SCRIPT>
-<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A>
-<A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A>
- <% mt('in queue') |h %>
-%# fetch list of queues in which the user can create tickets
-% my %queues = FS::TicketSystem->queues('', 'CreateTicket');
-% if( $conf->exists('ticket_system-force_default_queueid') ) {
-<B><% $queues{$new_param{'Queue'}} %></B>
-<INPUT TYPE="hidden" NAME="Queue" VALUE="<% $new_param{'Queue'} %>">
-% }
-% else {
-<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()">
-% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) {
- <OPTION VALUE="<% $queueid %>"
- <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
- ><% $queues{$queueid} |h %>
-% }
-</SELECT>
-<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT>
-% }
-</FORM>
- |
-View
-<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> |
-<A HREF="<% $res_link %>"><% mt('resolved') |h %></A>
-<BR>
-
-<& /elements/table-grid.html &>
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor = '';
-
-<TR>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Queue') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Owner') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Due') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Estimated Time') |h %></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Priority') |h %></TH>
-% if ( $ss_priority ) {
- <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Customer Priority') |h %></TH>
-% }
-</TR>
-
-% foreach my $ticket ( @tickets ) {
-% my $href = FS::TicketSystem->href_ticket($ticket->{id});
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-
- <TR>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <A HREF=<%$href%>><% $ticket->{id} %></A>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <A HREF=<%$href%>><% $ticket->{subject} %></A>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{status} %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{queue} %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{owner} %>
- </TD>
-
- <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $date_formatter->($ticket->{due}) %>
- </TD>
-
- <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{timeestimated} %>
- </TD>
-
- <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{content}
- ? $ticket->{content}.' ('.$ticket->{priority}.')'
- : $ticket->{priority}
- %>
- </TD>
-
-% if ( $ss_priority ) {
- <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
- <% $ticket->{"CF.{$ss_priority}"} %>
- </TD>
-% }
-
- </TR>
-
-% }
-
-</TABLE>
-
-<%init>
-use Date::Format 'time2str';
-use Date::Parse 'str2time';
-
-my( $conf ) = new FS::Conf;
-my( $cust_main ) = @_;
-my( @tickets ) = $cust_main->tickets;
-
-my $open_link = FS::TicketSystem->href_customer_tickets($cust_main->custnum);
-my $openlabel = join('/', FS::TicketSystem->statuses );
-
-my $res_link = FS::TicketSystem->href_customer_tickets(
- $cust_main->custnum,
- { 'statuses' => [ 'resolved' ] }
- );
-
-my( $new_base, %new_param ) =
- FS::TicketSystem->href_params_new_ticket( $cust_main );
-
-my $new_link = FS::TicketSystem->href_new_ticket( $cust_main );
-
-my $ss_priority = FS::TicketSystem->selfservice_priority;
-if ( $ss_priority ) {
- my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1;
- use sort 'stable';
- # sort in the following way:
- @tickets = sort {
- # within a severity level...
- ( $a->{'content'} eq $b->{'content'} ) ? (
- # no-priority tickets sort last
- (
- ($a->{'_selfservice_priority'} eq '') <=>
- ($b->{'_selfservice_priority'} eq '')
- ) ||
- # otherwise obey ticket_system-priority_reverse
- ( $dir *
- ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'})
- )
- ) : 0; # but don't rearrange between severity levels
- } @tickets;
-}
-
-my $format = $conf->config('date_format') || '%Y-%m-%d';
-
-my $date_formatter = sub {
- my $time = str2time($_[0], 'GMT');
- # exclude times within 24 hours of zero
- ($time > 86400) ? time2str($format, $time) : '';
-};
-
-</%init>
+% my $cust_main = shift;
+<& /elements/table-tickets.html, object => $cust_main &>
diff --git a/httemplate/view/cust_main_statement-pdf.cgi b/httemplate/view/cust_main_statement-pdf.cgi
index 7a0e19838..7c2c20799 100755
--- a/httemplate/view/cust_main_statement-pdf.cgi
+++ b/httemplate/view/cust_main_statement-pdf.cgi
@@ -23,13 +23,17 @@ my $cust_main = qsearchs({
'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
});
die "Customer #$custnum not found!" unless $cust_main;
+my $cust_bill = ($cust_main->cust_bill)[-1]
+ or die "Customer #$custnum has no invoices!";
my $cust_statement = FS::cust_statement->new({
'custnum' => $custnum,
- 'statementnum' => 'ALL', #magic
+# 'statementnum' => 'ALL', #magic
+ 'invnum' => $cust_bill->invnum,
'_date' => time,
});
+
my $pdf = $cust_statement->print_pdf( '', $templatename );
http_header('Content-Type' => 'application/pdf' );
diff --git a/httemplate/view/cust_msg.html b/httemplate/view/cust_msg.html
index ec584605d..67ceef799 100755
--- a/httemplate/view/cust_msg.html
+++ b/httemplate/view/cust_msg.html
@@ -22,7 +22,7 @@ function toggle_display(obj) {
<INPUT type="radio" name="what_to_show" onchange="toggle_display(this)" value="body"> Body
</FORM>
</TR>
-<TR><TD colspan=2 style="text-align:center">
+<TR><TD colspan=2 style="text-align:left">
<TEXTAREA id="content-header" style="font-family:monospace"
readonly=1 cols=80 rows=20>
<% encode_entities($cust_msg->header) %>
diff --git a/httemplate/view/directions.html b/httemplate/view/directions.html
index 599d049c2..f14a11a07 100644
--- a/httemplate/view/directions.html
+++ b/httemplate/view/directions.html
@@ -22,7 +22,6 @@ body { height: 100%; margin: 0px; padding: 0px }
#map_canvas {
height: 100%;
- margin-right: 320px;
}
#directions_panel {
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index 3130c73a0..04d2b2962 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -114,6 +114,10 @@ function areyousure(href) {
% }
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>
diff --git a/httemplate/view/elements/tr-svc_export_machine.html b/httemplate/view/elements/tr-svc_export_machine.html
new file mode 100644
index 000000000..1ba8d74a1
--- /dev/null
+++ b/httemplate/view/elements/tr-svc_export_machine.html
@@ -0,0 +1,27 @@
+% foreach my $part_export (@part_export) {
+% my $label = ( $part_export->exportname
+% ? $part_export->exportname
+% : $part_export->label
+% ).
+% ' hostname';
+%
+% my $svc_export_machine = qsearchs('svc_export_machine', {
+% 'svcnum' => $opt{svc}->svcnum,
+% 'exportnum' => $part_export->exportnum,
+% });
+
+ <& tr.html,
+ 'label' => $label,
+ 'value' => $svc_export_machine
+ ? $svc_export_machine->part_export_machine->machine
+ : '',
+ &>
+% }
+<%init>
+
+my %opt = @_;
+
+my @part_export = grep { $_->machine eq '_SVC_MACHINE' }
+ $opt{part_svc}->part_export;
+
+</%init>
diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html
index 9e85348af..6c4595dbe 100644
--- a/httemplate/view/prospect_main.html
+++ b/httemplate/view/prospect_main.html
@@ -64,6 +64,38 @@
<BR>
+% if ( $curuser->access_right('Generate quotation') ) {
+ <FONT CLASS="fsinnerbox-title"><% mt( 'Quotations' ) |h %></FONT>
+ <A HREF="<%$p%>edit/quotation.html?prospectnum=<% $prospectnum %>">New quotation</A>
+% my @quotations = $prospect_main->quotation;
+% if ( @quotations ) {
+ <& /elements/table-grid.html &>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">#</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Date') |h %></TH>
+ </TR>
+% foreach my $quotation (@quotations) {
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+% my $a = qq(<A HREF="$p/view/quotation.html?quotationnum=). #"
+% $quotation->quotationnum. '">';
+ <TR>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $a %><% $quotation->quotationnum %></A></TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $a %><% time2str($date_format, $quotation->_date) |h %></A></TD>
+ </TR>
+% }
+ </TABLE>
+% }
+ <BR><BR>
+% }
+
+
% if ( $curuser->access_right('Qualify service') ) {
<% include( '/elements/popup_link-prospect_main.html',
'action' => $p. 'misc/qual.html',
@@ -80,6 +112,7 @@
<BR><BR>
% }
+<!--
<% ntable("#cccccc") %>
<TR>
@@ -87,6 +120,7 @@
</TR>
</TABLE>
+-->
<%init>
@@ -95,6 +129,10 @@ my $curuser = $FS::CurrentUser::CurrentUser;
die "access denied"
unless $curuser->access_right('View prospect');
+my $conf = new FS::Conf;
+
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
my $prospectnum;
if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
$prospectnum = $1;
diff --git a/httemplate/view/qual.cgi b/httemplate/view/qual.cgi
index 5c15ec0c3..56c2b61df 100644
--- a/httemplate/view/qual.cgi
+++ b/httemplate/view/qual.cgi
@@ -59,8 +59,10 @@
% $opt{'svcpart'} = $pkg_svc->svcpart if $pkg_svc;
% }
- <% include('/view/cust_main/order_pkg_link.html',
- $cust_or_prospect, %opt) %>
+ <& /elements/order_pkg_link.html,
+ 'cust_main' => $cust_or_prospect,
+ %opt
+ &>
% } elsif ($cust_or_prospect->prospectnum) {
diff --git a/httemplate/view/quotation.html b/httemplate/view/quotation.html
new file mode 100755
index 000000000..a88acf82b
--- /dev/null
+++ b/httemplate/view/quotation.html
@@ -0,0 +1,91 @@
+<& /elements/header.html, mt('Quotation View'), $menubar &>
+
+%#XXX link to order...
+
+<%doc>
+
+XXX resending quotations
+
+% if ( $curuser->access_right('Resend invoices') ) {
+
+ <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A>
+
+% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>"><% mt('Re-email this invoice') |h %></A>
+% }
+
+% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>"><% mt('Re-fax this invoice') |h %></A>
+% }
+
+ <BR><BR>
+
+% }
+
+</%doc>
+
+% if ( $curuser->access_right('Order customer package') ) {
+ <& /elements/order_pkg_link.html,
+ 'label' => emt('Add package'),
+ 'actionlabel' => emt('Add package'),
+ map { $_ => $quotation->$_ } qw( quotationnum custnum prospectnum )
+ &>
+% }
+
+% if ( $conf->exists('quotation_latex') ) {
+ | <A HREF="<% $p %>view/quotation-pdf.cgi?<% $link %>"><% mt('View typeset quotation PDF') |h %></A>
+ <BR><BR>
+% }
+
+% if ( $conf->exists('quotation_html') ) {
+ <% join('', $quotation->print_html() ) %>
+% } else {
+% die "quotation_html config missing";
+% }
+% #plaintext quotations? <PRE><% join('', $quotation->print_text() ) %></PRE>
+
+<& /elements/footer.html &>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#die "access denied"
+# unless $curuser->access_right('View quotations');
+
+my $quotationnum;
+my($query) = $cgi->keywords;
+if ( $query =~ /^(\d+)$/ ) {
+ $quotationnum = $1;
+} else {
+ $quotationnum = $cgi->param('quotationnum');
+}
+
+my $conf = new FS::Conf;
+
+my $quotation = qsearchs({
+ 'select' => 'quotation.*',
+ 'table' => 'quotation',
+ #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'quotationnum' => $quotationnum },
+ #'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Quotation #$quotationnum not found!" unless $quotation;
+
+my $menubar;
+if ( my $custnum = $quotation->custnum ) {
+ my $display_custnum = $quotation->cust_main->display_custnum;
+ $menubar = menubar(
+ emt("View this customer (#[_1])",$display_custnum) => "${p}view/cust_main.cgi?$custnum",
+ );
+} elsif ( my $prospectnum = $quotation->prospectnum ) {
+ $menubar = menubar(
+ emt("View this prospect (#[_1])",$prospectnum) => "${p}view/prospect_main.html?$prospectnum",
+ );
+}
+
+my $link = "quotationnum=$quotationnum";
+#$link .= ';template='. uri_escape($template) if $template;
+#$link .= ';notice_name='. $notice_name if $notice_name;
+
+
+</%init>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index 441e4945d..3ad21bb4d 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -47,6 +47,7 @@
<& svc_acct/basics.html,
'svc_acct' => $svc_acct,
'part_svc' => $part_svc,
+ 'cust_svc' => $cust_svc,
%gopt,
&>
@@ -75,6 +76,11 @@
<& elements/svc_export_settings.html, $svc_acct &>
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
+<BR>
<% joblisting({'svcnum'=>$svcnum}, 1) %>
<& /elements/footer.html &>
diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html
index 8f180b648..1cdf77615 100644
--- a/httemplate/view/svc_acct/basics.html
+++ b/httemplate/view/svc_acct/basics.html
@@ -1,6 +1,9 @@
<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
<& /view/elements/tr.html, label=>mt('Service'), value=>$part_svc->svc &>
+% if ( $opt{cust_svc}->agent_svcid ) {
+ <& /view/elements/tr.html, label=>mt('Legacy ID'), value=>$opt{cust_svc}->agent_svcid &>
+% }
<& /view/elements/tr.html, label=>mt('Username'), value=>$svc_acct->username &>
<& /view/elements/tr.html, label=>mt('Domain'), value=>$domain &>
@@ -53,6 +56,11 @@
&>
% }
+<& /view/elements/tr-svc_export_machine.html,
+ 'svc' => $svc_acct,
+ 'part_svc' => $part_svc,
+&>
+
% if ($svc_acct->uid ne '') {
<& /view/elements/tr.html, label=>mt('UID'), value=>$svc_acct->uid &>
% }
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
index 3938a3406..fcccd74b4 100755
--- a/httemplate/view/svc_domain.cgi
+++ b/httemplate/view/svc_domain.cgi
@@ -36,6 +36,10 @@
<% include('elements/svc_export_settings.html', $svc_domain) %>
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
index 77679d81c..72e553598 100644
--- a/httemplate/view/svc_external.cgi
+++ b/httemplate/view/svc_external.cgi
@@ -23,6 +23,11 @@
</TABLE></TD></TR></TABLE>
+
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<BR><% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
index 15b5ae56f..2cb78ebd5 100755
--- a/httemplate/view/svc_forward.cgi
+++ b/httemplate/view/svc_forward.cgi
@@ -53,6 +53,10 @@
<% include('elements/svc_export_settings.html', $svc_forward) %>
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
index 935d139e9..fbb02a00f 100644
--- a/httemplate/view/svc_www.cgi
+++ b/httemplate/view/svc_www.cgi
@@ -49,6 +49,10 @@
</TABLE>
<BR>
+% if ( $conf->config('ticket_system') ) {
+<& /elements/table-tickets.html, object => $cust_svc &>
+% }
+
<% joblisting({'svcnum'=>$svcnum}, 1) %>
<% include('/elements/footer.html') %>