summaryrefslogtreecommitdiff
path: root/httemplate/view
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate/view')
-rwxr-xr-xhttemplate/view/REAL_logo.cgi14
-rw-r--r--httemplate/view/attachment.html16
-rw-r--r--httemplate/view/bill_batch.cgi99
-rwxr-xr-xhttemplate/view/cust_bill-logo.cgi31
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi40
-rwxr-xr-xhttemplate/view/cust_bill-ps.cgi35
-rwxr-xr-xhttemplate/view/cust_bill.cgi151
-rwxr-xr-xhttemplate/view/cust_main.cgi282
-rwxr-xr-xhttemplate/view/cust_main/attachments.html156
-rw-r--r--httemplate/view/cust_main/billing.html258
-rw-r--r--httemplate/view/cust_main/change_history.html302
-rw-r--r--httemplate/view/cust_main/contacts.html122
-rw-r--r--httemplate/view/cust_main/misc.html139
-rwxr-xr-xhttemplate/view/cust_main/notes.html96
-rw-r--r--httemplate/view/cust_main/one_time_charge_link.html91
-rw-r--r--httemplate/view/cust_main/order_pkg_link.html16
-rwxr-xr-xhttemplate/view/cust_main/packages.html229
-rw-r--r--httemplate/view/cust_main/packages/location.html53
-rw-r--r--httemplate/view/cust_main/packages/package.html238
-rw-r--r--httemplate/view/cust_main/packages/services.html140
-rw-r--r--httemplate/view/cust_main/packages/status.html484
-rw-r--r--httemplate/view/cust_main/payment_history.html445
-rw-r--r--httemplate/view/cust_main/payment_history/credit.html156
-rw-r--r--httemplate/view/cust_main/payment_history/invoice.html45
-rw-r--r--httemplate/view/cust_main/payment_history/payment.html225
-rw-r--r--httemplate/view/cust_main/payment_history/refund.html50
-rw-r--r--httemplate/view/cust_main/payment_history/statement.html34
-rw-r--r--httemplate/view/cust_main/payment_history/voided_payment.html56
-rw-r--r--httemplate/view/cust_main/tickets.html117
-rw-r--r--httemplate/view/cust_pay.html162
-rw-r--r--httemplate/view/cust_pay_void.html1
-rw-r--r--httemplate/view/cust_refund.html142
-rwxr-xr-xhttemplate/view/cust_statement-pdf.cgi28
-rwxr-xr-xhttemplate/view/cust_statement.html79
-rw-r--r--httemplate/view/cust_svc.cgi23
-rw-r--r--httemplate/view/elements/svc_Common.html167
-rw-r--r--httemplate/view/elements/svc_export_settings.html34
-rw-r--r--httemplate/view/elements/tr.html9
-rw-r--r--httemplate/view/logo.cgi47
-rw-r--r--httemplate/view/prospect_main.html93
-rw-r--r--httemplate/view/svc_Common.html29
-rwxr-xr-xhttemplate/view/svc_acct.cgi141
-rw-r--r--httemplate/view/svc_acct/basics.html136
-rw-r--r--httemplate/view/svc_acct/change_svc.html21
-rw-r--r--httemplate/view/svc_acct/change_svc_form.html23
-rw-r--r--httemplate/view/svc_acct/communigate.html115
-rw-r--r--httemplate/view/svc_acct/hosting.html38
-rw-r--r--httemplate/view/svc_acct/radius_usage.html77
-rw-r--r--httemplate/view/svc_acct/usage.html27
-rw-r--r--httemplate/view/svc_broadband.cgi219
-rwxr-xr-xhttemplate/view/svc_domain.cgi82
-rw-r--r--httemplate/view/svc_domain/acct_defaults.html141
-rw-r--r--httemplate/view/svc_domain/basics.html151
-rw-r--r--httemplate/view/svc_domain/dns.html95
-rw-r--r--httemplate/view/svc_external.cgi65
-rwxr-xr-xhttemplate/view/svc_forward.cgi124
-rw-r--r--httemplate/view/svc_mailinglist.cgi71
-rw-r--r--httemplate/view/svc_phone.cgi153
-rw-r--r--httemplate/view/svc_www.cgi106
59 files changed, 6719 insertions, 0 deletions
diff --git a/httemplate/view/REAL_logo.cgi b/httemplate/view/REAL_logo.cgi
new file mode 100755
index 0000000..c269c7d
--- /dev/null
+++ b/httemplate/view/REAL_logo.cgi
@@ -0,0 +1,14 @@
+<% $conf->config_binary("logo.png", $agentnum) %>
+<%init>
+
+my $conf = new FS::Conf;
+
+my $agentnum = '';
+my @agentnums = $FS::CurrentUser::CurrentUser->agentnums;
+if ( scalar(@agentnums) == 1 ) {
+ $agentnum = $agentnums[0];
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
diff --git a/httemplate/view/attachment.html b/httemplate/view/attachment.html
new file mode 100644
index 0000000..5fc0539
--- /dev/null
+++ b/httemplate/view/attachment.html
@@ -0,0 +1,16 @@
+<% $attach->body %>
+<%init>
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $attachnum = $1 or die 'Invalid attachment number';
+$FS::CurrentUser::CurrentUser->access_right('Download attachment') or die 'access denied';
+
+my $attach = qsearchs('cust_attachment', { attachnum => $attachnum }) or die "Attachment not found: $attachnum";
+die 'access denied' if $attach->disabled;
+
+$m->clear_buffer;
+$r->content_type($attach->mime_type || 'text/plain');
+$r->headers_out->add('Content-Disposition' => 'attachment;filename=' . $attach->filename);
+
+
+</%init>
diff --git a/httemplate/view/bill_batch.cgi b/httemplate/view/bill_batch.cgi
new file mode 100644
index 0000000..3fca6eb
--- /dev/null
+++ b/httemplate/view/bill_batch.cgi
@@ -0,0 +1,99 @@
+% if($magic eq 'print') {
+<% include('/elements/header.html', "Download Batch") %>
+<FORM NAME="OneTrueForm">
+<INPUT TYPE="hidden" NAME="batchnum" VALUE="<% $batchnum %>">
+% $cgi->delete('magic');
+<% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [ 'batchnum' ],
+ $p.'misc/process/bill_batch-print.html',
+ {'url' => $cgi->self_url . ';magic=download'},
+ '',
+) %></FORM>
+<SCRIPT TYPE="text/javascript">process();</SCRIPT>
+<% include('/elements/footer.html') %>
+% }
+%
+% elsif($magic eq 'download') {
+% $m->clear_buffer;
+% $r->content_type('application/pdf');
+% $r->headers_out->add('Content-Disposition' => 'attachment;filename="invoice_batch_'.$batchnum.'.pdf"');
+<% $batch->pdf %>
+% $batch->pdf('');
+% my $error = $batch->replace;
+% warn "error deleting cached PDF: '$error'\n" if $error;
+% }
+% else {
+<% include('/search/elements/search.html',
+ 'title' => $close ?
+ "Batch $batchnum closed." :
+ "Invoice Batch $batchnum",
+ 'name' => 'invoices',
+ 'query' => { 'table' => 'cust_bill_batch',
+ 'select' => join(', ',
+ 'cust_bill.*',
+ FS::UI::Web::cust_sql_fields(),
+ 'cust_main.custnum AS cust_main_custnum',
+ ),
+ 'hashref' => { },
+ 'addl_from' =>
+ 'LEFT JOIN cust_bill USING ( invnum ) '.
+ 'LEFT JOIN cust_main USING ( custnum )',
+ 'extra_sql' => '',
+ " WHERE batchnum = $batchnum",
+ },
+ 'count_query' => "SELECT COUNT(*) FROM cust_bill_batch WHERE batchnum = $batchnum",
+ 'html_init' => $html_init,
+ 'header' => [ 'Invoice #',
+ 'Amount',
+ 'Date',
+ 'Customer',
+ ],
+ 'fields' => [ sub { shift->cust_bill->display_invnum },
+ sub { sprintf($money_char.'%.2f',
+ shift->cust_bill->charged ) },
+ sub { time2str('%b %d %Y',
+ shift->cust_bill->_date ) },
+ sub { shift->cust_bill->cust_main->name },
+ ],
+ 'align' => 'rrll',
+ 'links' => [ ($link) x 3, $clink,
+ ],
+ 'really_disable_download' => 1,
+) %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+my $conf = new FS::Conf;
+my $batch;
+my $batchnum = $cgi->param('batchnum');
+
+$batch = FS::bill_batch->by_key($batchnum);
+die "Batch '$batchnum' not found!\n" if !$batch;
+
+my $magic = $cgi->param('magic');
+my $html_init = '';
+
+my $close = $cgi->param('close');
+$batch->close if $close;
+
+if(!$magic) {
+ $cgi->param('magic' => 'print');
+ $cgi->delete('close');
+ $html_init = '<A HREF="'.$cgi->self_url.'">Download this batch</A><BR>';
+ if($batch->status eq 'O') {
+ $cgi->param('close' => 1);
+ $cgi->delete('magic');
+ $html_init .= '<A HREF="'.$cgi->self_url.'">Close this batch</A><BR>';
+ }
+ $html_init .= '<BR>';
+}
+
+my $link = [ "$p/view/cust_bill.cgi?", 'invnum' ];
+my $clink = [ "$p/view/cust_main.cgi?", 'custnum' ];
+my $money_char = $conf->config('money_char') || '$';
+
+</%init>
diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi
new file mode 100755
index 0000000..ad2ff54
--- /dev/null
+++ b/httemplate/view/cust_bill-logo.cgi
@@ -0,0 +1,31 @@
+<% $conf->config_binary("logo$templatename.png", $agentnum) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices')
+ or $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $templatename;
+my $agentnum = '';
+if ( $cgi->param('invnum') ) {
+ $templatename = $cgi->param('template') || $cgi->param('templatename');
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $cgi->param('invnum') } )
+ or die 'unknown invnum';
+ $agentnum = $cust_bill->cust_main->agentnum;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^([^\.\/]*)$/ or die 'illegal query';
+ $templatename = $1;
+}
+
+if ( $templatename && $conf->exists("logo_$templatename.png") ) {
+ $templatename = "_$templatename";
+} else {
+ $templatename = '';
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
new file mode 100755
index 0000000..51e47e0
--- /dev/null
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -0,0 +1,40 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $invnum =~ s/\.pdf//i;
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+my $pdf = $cust_bill->print_pdf(\%opt);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
new file mode 100755
index 0000000..881491f
--- /dev/null
+++ b/httemplate/view/cust_bill-ps.cgi
@@ -0,0 +1,35 @@
+<% $cust_bill->print_ps(\%opt) %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = ( $cgi->param('notice_name') || 'Invoice' );
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+http_header('Content-Type' => 'application/postscript' );
+
+</%init>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
new file mode 100755
index 0000000..ce8d96a
--- /dev/null
+++ b/httemplate/view/cust_bill.cgi
@@ -0,0 +1,151 @@
+<% include("/elements/header.html",'Invoice View', menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+% if ( $conf->exists('deleteinvoices')
+% && $curuser->access_right('Delete invoices' )
+% )
+% {
+
+ <SCRIPT TYPE="text/javascript">
+ function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <A HREF = "javascript:areyousure(
+ '<%$p%>misc/delete-cust_bill.html?<% $invnum %>',
+ 'Are you sure you want to delete this invoice?'
+ )"
+ TITLE = "Delete this invoice from the database completely"
+ >Delete this invoice</A>
+ <BR><BR>
+
+% }
+
+% if ( $cust_bill->owed > 0
+% && scalar( grep $payby{$_}, qw(BILL CASH WEST MCRD) )
+% && $curuser->access_right('Post payment')
+% && ! $conf->exists('pkg-balances')
+% )
+% {
+% my $s = 0;
+
+ Post
+
+% if ( $payby{'BILL'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=BILL;invnum=<% $invnum %>">check</A>
+% }
+
+% if ( $payby{'CASH'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=CASH;invnum=<% $invnum %>">cash</A>
+% }
+
+% if ( $payby{'WEST'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;invnum=<% $invnum %>">Western Union</A>
+% }
+
+% if ( $payby{'MCRD'} ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;invnum=<% $invnum %>">manual credit card</A>
+% }
+
+ payment against this invoice<BR><BR>
+
+% }
+
+% if ( $curuser->access_right('Resend invoices') ) {
+
+ <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>">Re-print this invoice</A>
+
+% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>">Re-email this invoice</A>
+% }
+
+% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>">Re-fax this invoice</A>
+% }
+
+ <BR><BR>
+
+% }
+
+% if ( $conf->exists('invoice_latex') ) {
+
+ <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>">View typeset invoice PDF</A>
+ <BR><BR>
+% }
+
+% my $br = 0;
+% if ( $cust_bill->num_cust_event ) { $br++;
+<A HREF="<%$p%>search/cust_event.html?invnum=<% $cust_bill->invnum %>">(&nbsp;View invoice events&nbsp;)</A>
+% }
+
+% if ( $cust_bill->num_cust_bill_event ) { $br++;
+<A HREF="<%$p%>search/cust_bill_event.cgi?invnum=<% $cust_bill->invnum %>">(&nbsp;View deprecated, old-style invoice events&nbsp;)</A>
+% }
+
+<% $br ? '<BR><BR>' : '' %>
+
+% if ( $conf->exists('invoice_html') ) {
+ <% join('', $cust_bill->print_html(\%opt) ) %>
+% } else {
+ <PRE><% join('', $cust_bill->print_text(\%opt) ) %></PRE>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices');
+
+my( $invnum, $template, $notice_name );
+my($query) = $cgi->keywords;
+if ( $query =~ /^((.+)-)?(\d+)$/ ) {
+ $template = $2;
+ $invnum = $3;
+ $notice_name = 'Invoice';
+} else {
+ $invnum = $cgi->param('invnum');
+ $template = $cgi->param('template');
+ $notice_name = $cgi->param('notice_name');
+}
+
+my %opt = (
+ 'template' => $template,
+ 'notice_name' => $notice_name,
+);
+
+my $conf = new FS::Conf;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my $cust_bill = qsearchs({
+ 'select' => 'cust_bill.*',
+ 'table' => 'cust_bill',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'invnum' => $invnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+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;
+
+</%init>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
new file mode 100755
index 0000000..f6bef43
--- /dev/null
+++ b/httemplate/view/cust_main.cgi
@@ -0,0 +1,282 @@
+<% include('/elements/header.html', {
+ 'title' => "Customer: ". $cust_main->name,
+ 'nobr' => 1,
+ })
+%>
+<BR>
+% my @part_tag = $cust_main->part_tag;
+% if ( $conf->config('cust_tag-location') eq 'top' && @part_tag ) {
+<TABLE STYLE="margin-bottom:8px" CELLSPACING=2>
+% foreach my $part_tag ( @part_tag ) {
+<TR>
+ <TD>
+ <FONT SIZE="+1"
+ <% length($part_tag->tagcolor)
+ ? 'STYLE="background-color:#'.$part_tag->tagcolor.'"'
+ : ''
+ %>><% $part_tag->tagname.': '. $part_tag->tagdesc |h %></FONT>
+ </TD>
+</TR>
+% }
+</TABLE>
+% }
+
+<% include('/elements/menubar.html',
+ { 'newstyle' => 1,
+ 'selected' => $viewname{$view},
+ 'url_base' => $cgi->url. "?custnum=$custnum;show=",
+ },
+ %views,
+ )
+%>
+<DIV CLASS="fstabcontainer">
+
+<% include('/elements/init_overlib.html') %>
+
+<SCRIPT TYPE="text/javascript">
+function areyousure(href, message) {
+ if (confirm(message) == true)
+ window.location.href = href;
+}
+</SCRIPT>
+
+% if ( $view eq 'basics' || $view eq 'jumbo' ) {
+
+% if ( $curuser->access_right('Edit customer') ) {
+ <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> |
+% }
+
+% if ( $curuser->access_right('Cancel customer')
+% && $cust_main->ncancelled_pkgs
+% ) {
+
+ <% include( '/elements/popup_link-cust_main.html',
+ { 'action' => $p. 'misc/cancel_cust.html',
+ 'label' => 'Cancel&nbsp;this&nbsp;customer',
+ 'actionlabel' => 'Confirm Cancellation',
+ 'color' => '#ff0000',
+ 'cust_main' => $cust_main,
+ 'width' => 616, #make room for reasons
+ }
+ )
+ %> |
+
+% }
+
+% if ( $conf->exists('deletecustomers')
+% && $curuser->access_right('Delete customer')
+% ) {
+ <A HREF="<% $p %>misc/delete-customer.cgi?<% $custnum%>">Delete this customer</A> |
+% }
+
+% unless ( $conf->exists('disable_customer_referrals') ) {
+ <A HREF="<% $p %>edit/cust_main.cgi?referral_custnum=<% $custnum %>">Refer a new customer</A> |
+ <A HREF="<% $p %>search/cust_main.cgi?referral_custnum=<% $custnum %>">View this customer's referrals</A>
+% }
+
+<BR><BR>
+
+% if ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% ) {
+
+ <A HREF="<% $p %>search/cust_event.html?custnum=<% $custnum %>">View billing events for this customer</A>
+ <BR><BR>
+
+% }
+
+%my $signupurl = $conf->config('signupurl');
+%if ( $signupurl ) {
+ This customer's signup URL: <A HREF="<% $signupurl %>?ref=<% $custnum %>"><% $signupurl %>?ref=<% $custnum %></A><BR><BR>
+% }
+
+%if ( $conf->exists('maestro-status_test') ) {
+ <A HREF="<% $p %>misc/maestro-customer_status-test.html?<% $custnum %>">Test maestro status</A><BR><BR>
+% }
+
+<A NAME="cust_main"></A>
+<TABLE BORDER=0>
+<TR>
+ <TD VALIGN="top">
+ <% include('cust_main/contacts.html', $cust_main ) %>
+ </TD>
+ <TD VALIGN="top" STYLE="padding-left: 54px">
+ <% include('cust_main/misc.html', $cust_main ) %>
+% if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ <BR>
+ <% include('cust_main/billing.html', $cust_main ) %>
+% }
+
+ </TD>
+</TR>
+</TABLE>
+
+% }
+
+% if ( $view eq 'notes' || $view eq 'jumbo' ) {
+
+%if ( $cust_main->comments =~ /[^\s\n\r]/ ) {
+<BR>
+Comments
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+<TR>
+ <TD BGCOLOR="#ffffff">
+ <PRE><% encode_entities($cust_main->comments) %></PRE>
+ </TD>
+</TR>
+</TABLE></TABLE>
+<BR><BR>
+% }
+<A NAME="notes">
+% my $notecount = scalar($cust_main->notes());
+% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) {
+
+% unless ( $view eq 'notes' && $cust_main->comments !~ /[^\s\n\r]/ ) {
+ <BR>
+ <A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR>
+% }
+
+% if ( $curuser->access_right('Add customer note') &&
+% ! $conf->exists('cust_main-disable_notes')
+% ) {
+
+ <% include( '/elements/popup_link-cust_main.html',
+ 'label' => 'Add customer note',
+ 'action' => $p. 'edit/cust_main_note.cgi',
+ 'actionlabel' => 'Enter customer note',
+ 'cust_main' => $cust_main,
+ 'width' => 616,
+ 'height' => 538, #575
+ )
+ %>
+
+% }
+
+<BR>
+
+<% include('cust_main/notes.html', 'custnum' => $cust_main->custnum ) %>
+
+% }
+<BR>
+
+% if(! $conf->config('disable_cust_attachment')
+% and $curuser->access_right('Add attachment')) {
+<% include( '/elements/popup_link-cust_main.html',
+ 'label' => 'Attach file',
+ 'action' => $p.'edit/cust_main_attach.cgi',
+ 'actionlabel' => 'Upload file',
+ 'cust_main' => $cust_main,
+ 'width' => 480,
+ 'height' => 296,
+ )
+%>
+% }
+% if( $curuser->access_right('View attachments') ) {
+<% include('cust_main/attachments.html', 'custnum' => $cust_main->custnum ) %>
+% if ($cgi->param('show_deleted')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . '#notes'
+ %>"><I>(Show active attachments)</I></A>
+% }
+% elsif($curuser->access_right('View deleted attachments')) {
+<A HREF="<% $p.'view/cust_main.cgi?custnum=' . $cust_main->custnum .
+ ($view ? ";show=$view" : '') . ';show_deleted=1#notes'
+ %>"><I>(Show deleted attachments)</I></A>
+% }
+% }
+<BR>
+
+% }
+
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A><BR>
+% }
+
+% if ( $view eq 'tickets' || $view eq 'jumbo' ) {
+
+% if ( $conf->config('ticket_system') ) {
+ <% include('cust_main/tickets.html', $cust_main ) %>
+% }
+ <BR><BR>
+
+% }
+
+% if ( $view eq 'jumbo' ) { #XXX enable me && $curuser->access_right('View customer packages') {
+
+ <A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A><BR>
+% }
+
+% if ( $view eq 'packages' || $view eq 'jumbo' ) {
+
+% #XXX enable me# if ( $curuser->access_right('View customer packages') {
+<% include('cust_main/packages.html', $cust_main ) %>
+% #}
+
+% }
+
+% if ( $view eq 'jumbo' ) {
+ <BR><BR>
+ <A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
+% }
+
+% if ( $view eq 'payment_history' || $view eq 'jumbo' ) {
+
+% if ( $conf->config('payby-default') ne 'HIDE' ) {
+ <% include('cust_main/payment_history.html', $cust_main ) %>
+% }
+
+% }
+
+% if ( $view eq 'change_history' ) { # || $view eq 'jumbo'
+<% include('cust_main/change_history.html', $cust_main ) %>
+% }
+
+</DIV>
+<% include('/elements/footer.html') %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View customer');
+
+my $conf = new FS::Conf;
+
+my $custnum;
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $custnum = $1;
+} else {
+ die "No customer specified (bad URL)!" unless $cgi->keywords;
+ my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+}
+
+my $cust_main = qsearchs( {
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Customer not found!" unless $cust_main;
+
+#false laziness w/pref/pref.html and Conf.pm (cust_main-default_view)
+tie my %views, 'Tie::IxHash',
+ 'Basics' => 'basics',
+ 'Notes' => 'notes', #notes and files?
+;
+$views{'Tickets'} = 'tickets'
+ if $conf->config('ticket_system');
+$views{'Packages'} = 'packages';
+$views{'Payment History'} = 'payment_history'
+ unless $conf->config('payby-default' eq 'HIDE');
+$views{'Change History'} = 'change_history'
+ if $curuser->access_right('View customer history');
+$views{'Jumbo'} = 'jumbo';
+
+my %viewname = reverse %views;
+
+my $view = $cgi->param('show') || $curuser->default_customer_view;
+
+</%init>
diff --git a/httemplate/view/cust_main/attachments.html b/httemplate/view/cust_main/attachments.html
new file mode 100755
index 0000000..bdd4f59
--- /dev/null
+++ b/httemplate/view/cust_main/attachments.html
@@ -0,0 +1,156 @@
+% if ( scalar(@attachments) ) {
+
+ <% include('/elements/init_overlib.html') %>
+
+ <% include("/elements/table-grid.html") %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+% if ( $conf->exists('cust_main_note-display_times') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Filename</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Size</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+% if($cgi->param('show_deleted')) {
+% if ($curuser->access_right('View deleted attachments')) {
+% @attachments = grep { $_->disabled } @attachments;
+% }
+% else {
+% @attachments = ();
+% }
+% }
+% else {
+% @attachments = grep { not $_->disabled } @attachments;
+% }
+%
+% foreach my $attach (@attachments) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $pop = popurl(3);
+% my $attachnum = $attach->attachnum;
+% my $edit = '';
+% if($attach->disabled) { # then you can undelete it or purge it.
+% if ($curuser->access_right('Undelete attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "undelete=1",
+% 'Undelete attachment');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(undelete)</A>!;
+% }
+% if ($curuser->access_right('Purge attachment')) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;".
+% "purge=1",
+% 'Purge attachment',
+% 'Permanently remove this file?');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(purge)</A>!;
+% }
+% }
+% else { # you can download or edit it
+% if ($curuser->access_right('Edit attachment') ) {
+% my $clickjs = popup('edit/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum",
+% 'Edit attachment properties');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(edit)</A>!;
+% }
+% if($curuser->access_right('Delete attachment') ) {
+% my $clickjs = popup('edit/process/cust_main_attach.cgi?'.
+% "custnum=$custnum;attachnum=$attachnum;delete=1",
+% 'Delete attachment',
+% 'Delete this file?');
+% $edit .= qq!&nbsp; <A HREF="javascript:void(0);" $clickjs>(delete)</A>!;
+% }
+% if ($curuser->access_right('Download attachment') ) {
+% $edit .= qq!&nbsp; <A HREF="!.popurl(1).'attachment.html?'.$attachnum.qq!">(download)</A>!;
+% }
+% }
+
+ <TR>
+ <% note_datestr($attach,$conf,$bgcolor) %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->usernum ? $attach->access_user->name : $attach->otaker %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->filename %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->title %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $attach->mime_type %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% size_units( $attach->size ) %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $edit %>
+ </TD>
+ </TR>
+
+% } #end display notes
+
+</TABLE>
+
+% }
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied" if !$curuser->access_right('View attachments');
+my(%opt) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+die "Customer not found!" unless $cust_main;
+
+my (@attachments) = qsearch('cust_attachment', {'custnum' => $custnum});
+
+#subroutines
+
+sub note_datestr {
+ my($note, $conf, $bgcolor) = @_ or return '';
+ my $td = qq{<TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">};
+ my $format = "$td%b&nbsp;%o,&nbsp;%Y</TD>";
+ $format .= "$td%l:%M%P</TD>"
+ if $conf->exists('cust_main_note-display_times');
+ ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub size_units {
+ my $bytes = shift;
+ return $bytes if $bytes < 1024;
+ return int($bytes / 1024)."K" if $bytes < 1048576;
+ return int($bytes / 1048576)."M";
+}
+
+sub popup {
+ my ($url, $label, $confirm) = @_;
+ my $onclick =
+ include('/elements/popup_link_onclick.html',
+ 'action' => popurl(2).$url,
+ 'actionlabel' => $label,
+ 'width' => 510,
+ 'height' => 315,
+ 'frame' => 'top',
+ );
+ $onclick = qq!if(confirm('$confirm')) { $onclick }! if $confirm;
+ return qq!onclick="$onclick"!;
+}
+
+
+</%init>
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
new file mode 100644
index 0000000..54c180b
--- /dev/null
+++ b/httemplate/view/cust_main/billing.html
@@ -0,0 +1,258 @@
+Billing information
+%# If we can't see the unencrypted card, then bill now is an exercise in
+%# frustration (without some sort of job queue magic to send it to a secure
+%# machine, anyway)
+%if ( $FS::CurrentUser::CurrentUser->access_right('Bill customer now')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+%# (<A HREF="<% $p %>misc/bill.cgi?<% $cust_main->custnum %>">Bill now</A>)
+ (<% include('/elements/bill.html',
+ custnum => $cust_main->custnum,
+ label => 'Bill now',
+ url => $p.'view/cust_main.cgi?'.$cust_main->custnum,
+ ) %>)
+% }
+
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+
+%( my $balance = $cust_main->balance )
+% =~ s/^(\-?)(.*)$/<FONT SIZE=+1>$1<\/FONT>$money_char$2/;
+
+<TR>
+ <TD ALIGN="right">Balance due</TD>
+ <TD BGCOLOR="#ffffff"><B><% $balance %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Billing&nbsp;type</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) {
+
+
+ Credit&nbsp;card&nbsp;<% $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Card number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paymask %></TD>
+</TR>
+%
+%#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html
+%my( $mon, $year );
+%my $date = $cust_main->paydate || '12-2037';
+%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+%} else {
+% warn "unrecognized expiration date format: $date";
+% ( $mon, $year ) = ( '', '' );
+%}
+%
+
+<TR>
+ <TD ALIGN="right">Expiration</TD>
+ <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
+</TR>
+% if ( $cust_main->paystart_month ) {
+
+ <TR>
+ <TD ALIGN="right">Start date</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paystart_month. '/'. $cust_main->paystart_year %>
+ </TR>
+% } elsif ( $cust_main->payissue ) {
+
+ <TR>
+ <TD ALIGN="right">Issue #</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payissue %>
+ </TR>
+% }
+
+
+<TR>
+ <TD ALIGN="right">Name on card</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
+</TR>
+% } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') {
+% my( $account, $aba ) = split('@', $cust_main->paymask );
+
+
+ Electronic&nbsp;check&nbsp;<% $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">ABA/Routing code</TD>
+ <TD BGCOLOR="#ffffff"><% $aba %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Account number</TD>
+ <TD BGCOLOR="#ffffff"><% $account %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Account type</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paytype %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Bank name</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
+</TR>
+% if ( $conf->exists('show_bankstate') ) {
+<TR>
+ <TD ALIGN="right"><% $paystate_label %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->paystate || '&nbsp;&nbsp;&nbsp;' %></TD>
+</TR>
+% }
+% } elsif ( $cust_main->payby eq 'LECB' ) {
+% $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/;
+% my $payinfo = "$1-$2-$3";
+%
+
+
+ Phone&nbsp;bill&nbsp;billing
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Phone number</TD>
+ <TD BGCOLOR="#ffffff"><% $payinfo %></TD>
+</TR>
+% } elsif ( $cust_main->payby eq 'BILL' ) {
+
+
+ Billing
+ </TD>
+</TR>
+% if ( $cust_main->payinfo ) {
+
+<TR>
+ <TD ALIGN="right">P.O. </TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
+</TR>
+% }
+
+
+<TR>
+ <TD ALIGN="right">Attention</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD>
+</TR>
+% } elsif ( $cust_main->payby eq 'COMP' ) {
+
+
+ Complimentary
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Authorized&nbsp;by</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD>
+</TR>
+%
+%#false laziness w/above etc.
+%my( $mon, $year );
+%my $date = $cust_main->paydate || '12-2037';
+%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+%} else {
+% warn "unrecognized expiration date format: $date";
+% ( $mon, $year ) = ( '', '' );
+%}
+%
+
+<TR>
+ <TD ALIGN="right">Expiration</TD>
+ <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD>
+</TR>
+% }
+
+% my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups');
+<TR>
+ <TD ALIGN="right">Tax&nbsp;exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax ? 'yes' : 'no' %></TD>
+</TR>
+% foreach my $exempt_group ( @exempt_groups ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;exempt (<% $exempt_group %> taxes)</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->tax_exemption($exempt_group) ? 'yes' : 'no' %></TD>
+</TR>
+% }
+
+% if ( $conf->exists('enable_taxproducts') ) {
+<TR>
+ <TD ALIGN="right">Tax&nbsp;location</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->geocode('cch') %></TD>
+</TR>
+% }
+<TR>
+ <TD ALIGN="right">Postal&nbsp;invoices</TD>
+ <TD BGCOLOR="#ffffff">
+ <% ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">FAX&nbsp;invoices</TD>
+ <TD BGCOLOR="#ffffff">
+ <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'yes' : 'no' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Email&nbsp;invoices</TD>
+ <TD BGCOLOR="#ffffff">
+ <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Invoice&nbsp;terms</TD>
+ <TD BGCOLOR="#ffffff">
+ <% $cust_main->invoice_terms || 'Default ('. ( $conf->config('invoice_default_terms') || 'Payable upon receipt' ). ')' %>
+ </TD>
+</TR>
+
+% if ( $conf->exists('voip-cust_cdr_spools') ) {
+ <TR>
+ <TD ALIGN="right">Spool&nbsp;CDRs</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->spool_cdr ? 'yes' : 'no' %></TD>
+ </TR>
+% }
+
+% if ( $conf->exists('voip-cust_cdr_squelch') ) {
+ <TR>
+ <TD ALIGN="right">Print&nbsp;CDRs</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->squelch_cdr ? 'no' : 'yes' %></TD>
+ </TR>
+% }
+
+% if ( $conf->exists('voip-cust_email_csv_cdr') ) {
+ <TR>
+ <TD ALIGN="right">Email&nbsp;CDRs&nbsp;as&nbsp;CSV</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->email_csv_cdr ? 'yes' : 'no' %></TD>
+ </TR>
+% }
+
+% if ( $show_term || $cust_main->cdr_termination_percentage ) {
+ <TR>
+ <TD ALIGN="right">CDR termination settlement</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->cdr_termination_percentage %><% $cust_main->cdr_termination_percentage =~ /\d/ ? '%' : '' %></TD>
+ </TR>
+% }
+
+</TABLE></TD></TR></TABLE>
+<%once>
+
+my $paystate_label = FS::Msgcat::_gettext('paystate');
+$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+my @invoicing_list = $cust_main->invoicing_list;
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+#false laziness w/edit/cust_main/billing.html
+my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1";
+my $term_sth = dbh->prepare($term_sql) or die dbh->errstr;
+$term_sth->execute($cust_main->custnum) or die $term_sth->errstr;
+my $show_term = $term_sth->fetchrow_arrayref->[0];
+
+</%init>
diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html
new file mode 100644
index 0000000..53a79f4
--- /dev/null
+++ b/httemplate/view/cust_main/change_history.html
@@ -0,0 +1,302 @@
+% if ( int( time - (keys %years)[0] * 31556736 ) > $start ) {
+ Show:
+% my $chy = $cgi->param('change_history-years');
+% foreach my $y (keys %years) {
+% if ( $y == $years ) {
+ <FONT SIZE="+1"><% $years{$y} %></FONT>
+% } else {
+% $cgi->param('change_history-years', $y);
+ <A HREF="<% $cgi->self_url %>"><% $years{$y} %></A>
+% }
+% last if int( time - $y * 31556736 ) < $start;
+% }
+% $cgi->param('change_history-years', $chy);
+% }
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">User</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Item</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+</TR>
+
+% foreach my $item ( sort { $a->history_date <=> $b->history_date
+% #|| table order
+% || $a->historynum <=> $b->historynum
+% }
+% @history
+% )
+% {
+%
+% my $history_other = '';
+% my $act = $item->history_action;
+% if ( $act =~ /^replace/ ) {
+% my $pkey = $item->primary_key;
+% my $date = $item->history_date;
+% $history_other = qsearchs({
+% 'table' => $item->table,
+% 'hashref' => { $pkey => $item->$pkey(),
+% 'history_action' => $replace_other{$act},
+% 'historynum' => { 'op' => $replace_dir{$act},
+% 'value' => $item->historynum
+% },
+% },
+% 'extra_sql' => "
+% AND history_date $replace_direq{$act} $date
+% AND ($date $replace_op{$act} $fuzz) $replace_direq{$act} history_date
+% ORDER BY historynum $replace_ord{$act} LIMIT 1
+% ",
+% });
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $otaker = $item->history_user;
+% $otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+% $otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+% $otaker = '<i>job queue</i>' if $otaker eq 'fs_queue';
+ <% $otaker %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $d = time2str('%b %o, %Y', $item->history_date );
+% $d =~ s/ /&nbsp;/g;
+ <% $d %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $t = time2str('%r', $item->history_date );
+% $t =~ s/ /&nbsp;/g;
+ <% $t %>
+ </TD>
+ <TD ALIGN="center" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% my $label = $h_tables{$item->table};
+% $label = &{ $h_table_labelsub{$item->table} }( $item, $label )
+% if $h_table_labelsub{$item->table};
+ <% $label %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $action{$item->history_action} %>
+ </TD>
+ <TD ALIGN="left" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% join(', ',
+ map { my $value = ( $_ =~ /(^pay(info|cvv)|^ss|_password)$/ )
+ ? 'N/A'
+ : $item->get($_);
+ $value = substr($value, 0, 77).'...' if length($value) > 80;
+ $value = encode_entities($value);
+ "<I>$_</I>:<B>$value</B>";
+ }
+ grep { $history_other
+ ? ( $item->get($_) ne $history_other->get($_) )
+ : ( $item->get($_) =~ /\S/ )
+ }
+ grep { ! /^(history|custnum$)/i }
+ $item->fields
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+</TABLE>
+<%once>
+
+# length-switching
+
+tie my %years, 'Tie::IxHash',
+ .5 => '6 months',
+ 1 => '1 year',
+ 2 => '2 years',
+ 5 => '5 years',
+ 39 => 'all history',
+;
+
+# labeling history rows
+
+my %action = (
+ 'insert' => 'Insert', #'Create',
+ 'replace_old' => 'Change&nbsp;from',
+ 'replace_new' => 'Change&nbsp;to',
+ 'delete' => 'Remove',
+);
+
+# finding the other replace row
+
+my %replace_other = (
+ 'replace_new' => 'replace_old',
+ 'replace_old' => 'replace_new',
+);
+my %replace_dir = (
+ 'replace_new' => '<',
+ 'replace_old' => '>',
+);
+my %replace_direq = (
+ 'replace_new' => '<=',
+ 'replace_old' => '>=',
+);
+my %replace_op = (
+ 'replace_new' => '-',
+ 'replace_old' => '+',
+);
+my %replace_ord = (
+ 'replace_new' => 'DESC',
+ 'replace_old' => 'ASC',
+);
+
+my $fuzz = 5; #seems like a lot
+
+# which tables to search and what to call them
+
+tie my %tables, 'Tie::IxHash',
+ 'cust_main' => 'Customer',
+ 'cust_main_invoice' => 'Invoice destination',
+ 'cust_pkg' => 'Package',
+ #? or just svc_* ? 'cust_svc' =>
+ 'svc_acct' => 'Account',
+ 'radius_usergroup' => 'RADIUS group',
+ 'svc_domain' => 'Domain',
+ 'svc_www' => 'Hosting',
+ 'svc_forward' => 'Mail forward',
+ 'svc_broadband' => 'Broadband',
+ 'svc_external' => 'External service',
+ 'svc_phone' => 'Phone',
+ 'phone_device' => 'Phone device',
+ #? it gets provisioned anyway 'phone_avail' => 'Phone',
+;
+
+my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )';
+
+my %table_join = (
+ 'svc_acct' => $svc_join,
+ 'radius_usergroup' => $svc_join,
+ 'svc_domain' => $svc_join,
+ 'svc_www' => $svc_join,
+ 'svc_forward' => $svc_join,
+ 'svc_broadband' => $svc_join,
+ 'svc_external' => $svc_join,
+ 'svc_phone' => $svc_join,
+ 'phone_device' => $svc_join,
+);
+
+my %h_tables = map { ( "h_$_" => $tables{$_} ) } keys %tables;
+
+my %pkgpart = ();
+my $pkg_labelsub = sub {
+ my($item, $label) = @_;
+ $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg;
+ $label. ': <b>'. encode_entities($pkgpart{$item->pkgpart}). '</b>';
+};
+
+my $svc_labelsub = sub {
+ my($item, $label) = @_;
+ $label. ': <b>'. encode_entities($item->label($item->history_date)). '</b>';
+};
+
+my %h_table_labelsub = (
+ 'h_cust_pkg' => $pkg_labelsub,
+ 'h_svc_acct' => $svc_labelsub,
+ #'h_radius_usergroup' =>
+ 'h_svc_domain' => $svc_labelsub,
+ 'h_svc_www' => $svc_labelsub,
+ 'h_svc_forward' => $svc_labelsub,
+ 'h_svc_broadband' => $svc_labelsub,
+ 'h_svc_external' => $svc_labelsub,
+ 'h_svc_phone' => $svc_labelsub,
+ #'h_phone_device'
+);
+
+# cust_main
+# cust_main_invoice
+
+# cust_pkg
+# cust_pkg_option?
+# cust_pkg_detail
+# cust_pkg_reason? no
+
+#cust_svc
+#cust_svc_option?
+#svc_*
+# svc_acct
+# radius_usergroup
+# acct_snarf? is this even used?
+# svc_domain
+# domain_record
+# registrar
+# svc_forward
+# svc_www
+# svc_broadband
+# (virtual fields? eh... maybe when they're real)
+# svc_external
+# svc_phone
+# phone_device
+# phone_avail
+
+# future:
+
+# inventory_item (from services)
+# pkg_referral? (changed?)
+
+#random others:
+
+# cust_location?
+# cust_main-exemption?? (295.ca named tax exemptions)
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access deined"
+ unless $curuser->access_right('View customer history');
+
+# find out the beginning of this customer history, if possible
+my $h_insert = qsearchs({
+ 'table' => 'h_cust_main',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ 'history_action' => 'insert',
+ },
+ 'extra_sql' => 'ORDER BY historynum LIMIT 1',
+});
+my $start = $h_insert ? $h_insert->history_date : 0;
+
+# retreive the history
+
+my @history = ();
+
+my $years = $conf->config('change_history-years') || .5;
+if ( $cgi->param('change_history-years') =~ /^([\d\.]+)$/ ) {
+ $years = $1;
+}
+my $newer_than = int( time - $years * 31556736 ); #60*60*24*365.24
+
+local($FS::Record::nowarn_classload) = 1;
+
+foreach my $table ( keys %tables ) {
+ my @items = qsearch({
+ 'table' => "h_$table",
+ 'addl_from' => $table_join{$table},
+ 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, },
+ 'extra_sql' => ' AND custnum = '. $cust_main->custnum,
+ });
+ push @history, @items;
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html
new file mode 100644
index 0000000..e88c02e
--- /dev/null
+++ b/httemplate/view/cust_main/contacts.html
@@ -0,0 +1,122 @@
+% my %which = (
+% '' => 'Billing',
+% 'ship_' => 'Service',
+% );
+% foreach my $which ( '', 'ship_' ) {
+% my $pre = $cust_main->get("${which}last") ? $which : '';
+
+<% $which{$which} %> address
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+<TR>
+ <TD ALIGN="right">Contact name</TD>
+ <TD COLSPAN=5 BGCOLOR="#ffffff">
+ <% $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") %>
+ </TD>
+% if ( $which eq '' && $conf->exists('show_ss') ) {
+ <TD ALIGN="right">SS#</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->masked('ss') || '&nbsp' %></TD>
+% }
+</TR>
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Address</TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") %></TD>
+</TR>
+
+% if ( $cust_main->get("${pre}address2") ) {
+% my $address2_label =
+% ( $conf->exists('cust_main-require_address2')
+% && ! ( $pre xor $cust_main->has_ship_address )
+% )
+% ? 'Unit&nbsp;#'
+% : '&nbsp;';
+
+ <TR>
+ <TD ALIGN="right"><% $address2_label %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") %></TD>
+ </TR>
+
+% }
+
+<TR>
+ <TD ALIGN="right">City</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") %></TD>
+% if ( $cust_main->get("${pre}county") ) {
+ <TD ALIGN="right">County</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}county") %></TD>
+% }
+ <TD ALIGN="right">State</TD>
+ <TD BGCOLOR="#ffffff"><% state_label( $cust_main->get("${pre}state"), $cust_main->get("${pre}country") ) %></TD>
+ <TD ALIGN="right">Zip</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}zip") %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Country</TD>
+ <TD BGCOLOR="#ffffff"><% code2country( $cust_main->get("${pre}country") ) %></TD>
+</TR>
+<TR>
+ <TD ALIGN="right"><% $daytime_label %></TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% include('/elements/phonenumber.html',
+ $cust_main->get("${pre}daytime"),
+ 'callable'=>1
+ )
+ %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right"><% $night_label %></TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% include('/elements/phonenumber.html',
+ $cust_main->get("${pre}night"),
+ 'callable'=>1
+ )
+ %>
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=3 BGCOLOR="#ffffff">
+ <% $cust_main->get("${pre}fax") || '&nbsp' %>
+ </TD>
+</TR>
+% if ( $which eq '' && $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>
+% }
+</TABLE></TD></TR></TABLE>
+% if ( $which ne 'ship_' ) {
+<BR>
+% }
+% }
+
+<%once>
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day&nbsp;Phone'
+ : FS::Msgcat::_gettext('daytime');
+my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/
+ ? 'Night&nbsp;Phone'
+ : FS::Msgcat::_gettext('night');
+my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
+ ? 'Driver&rsquo;s&nbsp;License'
+ : FS::Msgcat::_gettext('stateid');
+my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
+ ? 'Driver&rsquo;s&nbsp;License State'
+ : FS::Msgcat::_gettext('stateid_state');
+
+</%once>
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+
+</%init>
+
diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html
new file mode 100644
index 0000000..6e90a0b
--- /dev/null
+++ b/httemplate/view/cust_main/misc.html
@@ -0,0 +1,139 @@
+<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Customer&nbsp;number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->display_custnum %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Status</TD>
+ <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% ucfirst($cust_main->status) %></B></FONT></TD>
+</TR>
+
+% my @part_tag = $cust_main->part_tag;
+% if ( $conf->config('cust_tag-location') =~ /^(cust_misc|)$/ && @part_tag ) {
+<TR>
+ <TD ALIGN="right">Tags</TD>
+ <TD BGCOLOR="#ffffff">
+% foreach my $part_tag ( @part_tag ) {
+ <FONT <% length($part_tag->tagcolor)
+ ? 'STYLE="background-color:#'.$part_tag->tagcolor.'"'
+ : '' %>
+ ><% $part_tag->tagname.': '. $part_tag->tagdesc |h %></FONT>
+ <BR>
+% }
+ </TD>
+</TR>
+% }
+
+%unless ( scalar(@agentnums) == 1
+% && !$curuser->access_right('View customers of all agents') ) {
+% my $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } );
+ <TR>
+ <TD ALIGN="right">Agent</TD>
+ <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD>
+ </TR>
+% }
+
+% if ( $cust_main->agent_custid
+% && ! $conf->exists('cust_main-default_agent_custid') ) {
+
+<TR>
+ <TD ALIGN="right">Agent customer ref#</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->agent_custid %></TD>
+</TR>
+%
+% }
+
+% #if ( $cust_main->classnum ) {
+ <TR>
+ <TD ALIGN="right">Class</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->classname || '(none)' %></TD>
+ </TR>
+% #}
+
+% unless ( FS::part_referral->num_part_referral == 1 ) {
+% my $referral = qsearchs('part_referral', {
+% 'refnum' => $cust_main->refnum
+% } );
+
+<TR>
+ <TD ALIGN="right">Advertising&nbsp;source</TD>
+ <TD BGCOLOR="#ffffff"><% $referral->refnum %>: <% $referral->referral%></TD>
+</TR>
+% }
+
+
+<TR>
+ <TD ALIGN="right">Referring&nbsp;Customer</TD>
+ <TD BGCOLOR="#ffffff">
+%
+% my $referring_cust_main = '';
+% if ( $cust_main->referral_custnum
+% && ( $referring_cust_main =
+% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+% )
+% ) {
+%
+
+
+<A HREF="<% popurl(1) %>cust_main.cgi?<% $cust_main->referral_custnum %>"><%$cust_main->referral_custnum %>:
+<%
+ ( $referring_cust_main->company
+ ? $referring_cust_main->company. ' ('.
+ $referring_cust_main->last. ', '. $referring_cust_main->first.
+ ')'
+ : $referring_cust_main->last. ', '. $referring_cust_main->first
+ )
+%></A>
+% }
+
+
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Order taker</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->otaker %></TD>
+</TR>
+
+ <TR>
+ <TD ALIGN="right">Signup Date</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->signupdate ? time2str($date_format, $cust_main->signupdate) : '' %></TD>
+ </TR>
+
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+% my $dt = $cust_main->birthdate ne ''
+% ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate,
+% 'time_zone' =>'floating',
+% )
+% : '';
+
+ <TR>
+ <TD ALIGN="right">Date of Birth</TD>
+ <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD>
+ </TR>
+
+% }
+
+% if ( $conf->exists('cust_main-require_censustract') ) {
+
+ <TR>
+ <TD ALIGN="right">Census tract</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_main->censustract %></TD>
+ </TR>
+
+% }
+
+</TABLE></TD></TR></TABLE>
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+my $date_format = ($conf->config('date_format') || "%m/%d/%Y");
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my @agentnums = $curuser->agentnums;
+
+</%init>
diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html
new file mode 100755
index 0000000..1283b19
--- /dev/null
+++ b/httemplate/view/cust_main/notes.html
@@ -0,0 +1,96 @@
+% if ( scalar(@notes) ) {
+
+ <% include('/elements/init_overlib.html') %>
+
+ <% include("/elements/table-grid.html") %>
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+% if ( $conf->exists('cust_main_note-display_times') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Time</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Person</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Note</TH>
+% if ($curuser->access_right('Edit customer note') ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">&nbsp;</TH>
+% }
+ </TR>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+%
+% foreach my $note (@notes) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $pop = popurl(3);
+% my $notenum = $note->notenum;
+% my $onclick = include( '/elements/popup_link_onclick.html',
+% 'action' => popurl(2).
+% 'edit/cust_main_note.cgi'.
+% "?custnum=$custnum".
+% ";notenum=$notenum",
+% 'actionlabel' => 'Edit customer note',
+% 'width' => 616,
+% 'height' => 538, #575
+% 'frame' => 'top',
+% );
+% my $clickjs = qq!onclick="$onclick"!;
+%
+% my $edit = '';
+% if ($curuser->access_right('Edit customer note') ) {
+% $edit = qq! <A HREF="javascript:void(0);" $clickjs>(edit)</A>!;
+% }
+
+ <TR>
+ <% note_datestr($note,$conf,$bgcolor) %>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ &nbsp;<% $note->usernum ? $note->access_user->name : $note->otaker %>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $note->comments | defang %>
+ </TD>
+% if($edit) {
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $edit %></TD>
+% }
+ </TR>
+
+% } #end display notes
+
+</TABLE>
+
+% }
+<%init>
+
+use HTML::Defang;
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my(%opt) = @_;
+
+my $custnum = $opt{'custnum'};
+
+my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+die "Custimer not found!" unless $cust_main;
+
+my (@notes) = $cust_main->notes();
+
+#subroutines
+
+sub note_datestr {
+ my($note, $conf, $bgcolor) = @_ or return '';
+ my $td = qq{<TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">};
+ my $format = "$td%b&nbsp;%o,&nbsp;%Y</TD>";
+ $format .= "$td%l:%M%P</TD>"
+ if $conf->exists('cust_main_note-display_times');
+ ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/one_time_charge_link.html b/httemplate/view/cust_main/one_time_charge_link.html
new file mode 100644
index 0000000..b3defa2
--- /dev/null
+++ b/httemplate/view/cust_main/one_time_charge_link.html
@@ -0,0 +1,91 @@
+<SCRIPT TYPE="text/javascript">
+
+function taxproductmagic(which) {
+
+ var str = '';
+ var elements = which.form.elements;
+ for (var i = 0; i<elements.length; i++) {
+
+ if (elements[i].name == 'taxproductnum'){
+ document.getElementById('taxproductnum').value = elements[i].value;
+ continue;
+ }
+ if (elements[i].name == 'taxproductnum_description'){
+ continue;
+ }
+
+ if (str.length){str += ';';}
+
+ var value = '';
+ if ( elements[i].type == 'checkbox' || elements[i].type == 'radio' ) {
+ if ( elements[i].checked == true ) {
+ value = elements[i].value;
+ //} else {
+ // value = '';
+ }
+ } else {
+ value = elements[i].value;
+ }
+ str += elements[i].name + '=' + escape(value);
+
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/browse/part_pkg_taxproduct.cgi?_type=select&id=taxproductnum&onclick=taxproductquickchargemagic&taxproductnum='+document.getElementById('taxproductnum').value, 1000, 400, 'tax_product_popup'), CAPTION, 'Select product', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxproductquickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxproductnum;taxproductnum=';
+ str += escape(document.getElementById('taxproductnum').value);
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+function taxoverridemagic(which) {
+ var str = '';
+ var elements = which.ownerDocument.QuickChargeForm.elements;
+ for (var i = 0; i<elements.length; i++) {
+ if (elements[i].name == 'tax_override'){
+ document.getElementById('tax_override').value = elements[i].value;
+ continue;
+ }
+ if (str.length){str += ';';}
+ str += elements[i].name + '=' + escape(elements[i].value);
+ }
+ document.getElementById('charge_storage').value = str;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/part_pkg_taxoverride.html?element_name=tax_override;onclick=taxoverridequickchargemagic;selected='+document.getElementById('tax_override').value, 1100, 600, 'tax_product_popup'), CAPTION, 'Edit product tax overrides', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK);
+}
+
+function taxoverridequickchargemagic() {
+ var str = document.getElementById('charge_storage').value;
+ if (str.length){str += ';';}
+ str += 'magic=taxoverride;tax_override=';
+ str += document.getElementById('tax_override').value;
+ cClick();
+ overlib( OLiframeContent('<% $p %>/edit/quick-charge.html?'+str, 545, 336, 'One-time charge'), CAPTION, 'One-time charge', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close');
+
+}
+
+</SCRIPT>
+
+<FORM NAME='quickcharge' STYLE="margin:0; padding:0; display:inline"><INPUT NAME="taxproductnum" ID="taxproductnum" TYPE="hidden"><INPUT NAME="tax_override" ID="tax_override" TYPE="hidden"><INPUT NAME="charge_storage" ID="charge_storage" TYPE="hidden"><INPUT NAME="taxproductnum_description" ID="taxproductnum_description" TYPE="hidden"></FORM>
+
+<% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/quick-charge.html?custnum='. $cust_main->custnum,
+ 'label' => 'One-time charge',
+ 'actionlabel' => 'One-time charge',
+ 'color' => '#333399',
+ 'width' => 763,
+ 'height' => 460, #more for more room for lines of add'l description?
+ })
+%>
+
+<%init>
+
+my($cust_main) = @_;
+
+</%init>
diff --git a/httemplate/view/cust_main/order_pkg_link.html b/httemplate/view/cust_main/order_pkg_link.html
new file mode 100644
index 0000000..30c86a7
--- /dev/null
+++ b/httemplate/view/cust_main/order_pkg_link.html
@@ -0,0 +1,16 @@
+<% include( '/elements/popup_link-cust_main.html',
+ 'action' => $p. 'misc/order_pkg.html',
+ 'label' => 'Order&nbsp;new&nbsp;package',
+ 'actionlabel' => 'Order new package',
+ 'color' => '#333399',
+ 'cust_main' => $cust_main,
+ 'closetext' => 'Close',
+ 'width' => 763,
+ 'height' => 538,
+ )
+%>
+<%init>
+
+my($cust_main) = @_;
+
+</%init>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
new file mode 100755
index 0000000..811ac3c
--- /dev/null
+++ b/httemplate/view/cust_main/packages.html
@@ -0,0 +1,229 @@
+% my $s = 0;
+% if ( $curuser->access_right('Order customer package') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('order_pkg_link.html', $cust_main) %>
+% }
+
+% if ( $curuser->access_right('One-time charge')
+% && $conf->config('payby-default') ne 'HIDE'
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('one_time_charge_link.html', $cust_main) %>
+% }
+
+% if ( $curuser->access_right('Bulk change customer packages') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pkg.cgi?<% $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services)
+% }
+
+<BR><BR>
+
+<TABLE>
+ <TR>
+ <TD ALIGN="left">
+
+% if ( @$packages ) {
+
+Current packages
+% }
+% if ( $cust_main->num_cancelled_pkgs ) {
+% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+% || ( $conf->exists('hidecancelledpackages')
+% && ! $cgi->param('showcancelledpackages')
+% )
+% )
+% {
+% my $prev = $cgi->param('showcancelledpackages');
+% $cgi->param('showcancelledpackages', 1);
+ ( <a href="<% $cgi->self_url %>">show
+% $cgi->param('showcancelledpackages', $prev);
+% } else {
+% $cgi->param('showcancelledpackages', 0);
+ ( <a href="<% $cgi->self_url %>">hide
+% $cgi->param('showcancelledpackages', 1);
+% }
+
+ cancelled packages</a> )
+% }
+% if ( $num_old_packages ) {
+% $cgi->param('showoldpackages', 1);
+ ( <a href="<% $cgi->self_url %>">show old packages</a> )
+% } elsif ( $cgi->param('showoldpackages') ) {
+% $cgi->param('showoldpackages', 0);
+ ( <a href="<% $cgi->self_url %>">hide old packages</a> )
+% }
+
+ </TD>
+ <TD ALIGN="right">
+ <A HREF="<%$p%>search/report_cust_pkg.html?custnum=<% $cust_main->custnum %>">Package reports</A><BR>
+ Service reports:
+ <A HREF="<%$p%>search/report_svc_acct.html?custnum=<% $cust_main->custnum %>">accounts</A>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN=2>
+
+% if ( @$packages ) {
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Package</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+% if ( $show_location ) {
+ <TH CLASS="grid" BGCOLOR="#cccccc">Location</TH>
+% }
+ <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH>
+</TR>
+
+% #$FS::cust_pkg::DEBUG = 2;
+% foreach my $cust_pkg (@$packages) {
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my %iopt = (
+% 'bgcolor' => $bgcolor,
+% 'cust_pkg' => $cust_pkg,
+% 'part_pkg' => $cust_pkg->part_pkg,
+% %conf_opt,
+% );
+%
+
+ <!--pkgnum: <% $cust_pkg->pkgnum %>-->
+ <TR>
+ <% include('packages/package.html', %iopt) %>
+ <% include('packages/status.html', %iopt) %>
+% if ( $show_location ) {
+ <% include('packages/location.html', %iopt) %>
+% }
+ <% include('packages/services.html', %iopt) %>
+ </TR>
+
+% }
+
+</TABLE>
+
+% } else {
+<BR>
+% }
+
+ </TD>
+ </TR>
+</TABLE>
+
+% if ( $cgi->param('fragment') =~ /^cust_pkg(\d+)$/ ) {
+ <SCRIPT>
+ // IE-specific hack. other browsers listen to #fragments
+ // is this even working? or is the #target redirection just working cause
+ // we set the URL params differently?
+ var el = document.getElementById( 'cust_pkg<% $1 %>' );
+ if ( el ) el.scrollIntoView(true);
+ </SCRIPT>
+% }
+
+<%init>
+
+my( $cust_main ) = @_;
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my( $packages, $num_old_packages ) = get_packages($cust_main, $conf);
+
+my $show_location = $conf->exists('cust_pkg-always_show_location')
+ || ( grep $_->locationnum, @$packages ); # ? '1' : '0';
+
+my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
+my %conf_opt = (
+ #for services.html and status.html
+ 'cust_pkg-display_times' => $conf->exists('cust_pkg-display_times'),
+
+ #for status.html
+ 'cust_pkg-show_autosuspend' => $conf->exists('cust_pkg-show_autosuspend'),
+ #for status.html pkg-balances
+ 'pkg-balances' => $conf->exists('pkg-balances'),
+ 'money_char' => ( $conf->config('money_char') || '$' ),
+
+ #for location.html
+ 'countrydefault' => $countrydefault,
+ 'statedefault' => ( scalar($conf->config('statedefault'))
+ || ($countrydefault eq 'US' ? 'CA' : '') ),
+ #for services.html
+ 'svc_external-skip_manual' => $conf->exists('svc_external-skip_manual'),
+ 'legacy_link' => $conf->exists('legacy_link'),
+ 'svc_broadband-manage_link' => scalar($conf->config('svc_broadband-manage_link')),
+ 'maestro-status_test' => $conf->exists('maestro-status_test'),
+);
+
+#subroutines
+
+sub get_packages {
+ my $cust_main = shift or return undef;
+ my $conf = shift;
+
+ my $method;
+ if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledpackages')
+ && ! $cgi->param('showcancelledpackages') )
+ )
+ {
+ $method = 'ncancelled_pkgs';
+ } else {
+ $method = 'all_pkgs';
+ }
+
+ my $cust_pkg_fields =
+ join(', ', map { "cust_pkg.$_ AS $_" } fields('cust_pkg') );
+
+ my $part_pkg_fields =
+ join(', ', map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') );
+
+ my $group_by =
+ join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
+ join(', ', map "part_pkg.$_", fields('part_pkg') );
+
+ my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
+ ' WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
+
+ my @packages = $cust_main->$method( {
+ 'select' => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
+ 'addl_from' => 'LEFT JOIN part_pkg USING ( pkgpart )',
+ } );
+ my $num_old_packages = scalar(@packages);
+
+ foreach my $cust_pkg ( @packages ) {
+ my %hash = $cust_pkg->hash;
+ my %part_pkg = map { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
+ grep { /^part_pkg_/ } keys %hash;
+ $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
+ }
+
+ unless ( $cgi->param('showoldpackages') ) {
+ my $years = $conf->config('cust_main-packages-years') || 2;
+ my $then = time - $years * 31556926; #60*60*24*365.2422 is close enough
+
+ my %hide = ( 'cancelled' => 'cancel',
+ 'one-time charge' => 'setup',
+ );
+
+ @packages =
+ grep { !exists($hide{$_->status}) or $_->get($hide{$_->status}) > $then
+ or $_->num_svcs #don't hide packages w/services
+ }
+ @packages;
+ }
+
+ $num_old_packages -= scalar(@packages);
+
+ ( \@packages, $num_old_packages );
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html
new file mode 100644
index 0000000..41155cb
--- /dev/null
+++ b/httemplate/view/cust_main/packages/location.html
@@ -0,0 +1,53 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+
+% unless ( $cust_pkg->locationnum ) {
+ <I><FONT SIZE=-1>(default service address)</FONT><BR>
+% }
+
+ <% $loc->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ )
+ %>
+
+% unless ( $cust_pkg->locationnum ) {
+ </I>
+% }
+
+% if ( ! $cust_pkg->get('cancel')
+% && $FS::CurrentUser::CurrentUser->access_right('Change customer package')
+% )
+% {
+ <FONT SIZE=-1>
+ (&nbsp;<%pkg_change_location_link($cust_pkg)%>&nbsp;)
+ </FONT>
+% }
+
+</TD>
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $countrydefault = $opt{'countrydefault'} || 'US';
+my $statedefault = $opt{'statedefault'}
+ || ($countrydefault eq 'US' ? 'CA' : '');
+
+my $loc = $cust_pkg->cust_location_or_main;
+
+sub pkg_change_location_link {
+ my $cust_pkg = shift;
+ my $pkgpart = $cust_pkg->pkgpart;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg.cgi?locationnum=-1;pkgpart=$pkgpart;".
+ "address1=;address2=;city=;county=;state=$statedefault;".
+ "zip=;country=$countrydefault",
+ 'label' => 'Change&nbsp;location',
+ 'actionlabel' => 'Change',
+ 'cust_pkg' => $cust_pkg,
+ );
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
new file mode 100644
index 0000000..3c486dd
--- /dev/null
+++ b/httemplate/view/cust_main/packages/package.html
@@ -0,0 +1,238 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+ <TR>
+ <TD COLSPAN=2>
+ <A NAME="cust_pkg<% $cust_pkg->pkgnum %>"
+ ID ="cust_pkg<% $cust_pkg->pkgnum %>"
+ ><% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B></A>
+ -
+ <% $part_pkg->custom_comment |h %>
+ </TD>
+ </TR>
+
+% if ( $cust_pkg->quantity > 1 ) {
+ <TR>
+ <TD COLSPAN=2>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity:
+ <B><% $cust_pkg->quantity %></B>
+ </TD>
+ </TR>
+% }
+
+ <TR>
+ <TD COLSPAN=2>
+ <FONT SIZE=-1>
+
+% unless ( $cust_pkg->get('cancel') ) {
+%
+% my $br = 0;
+% if ( $curuser->access_right('Change customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_change_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Edit customer package dates') ) {
+% $br=1;
+ (&nbsp;<%pkg_dates_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Discount customer package')
+% && $part_pkg->can_discount
+% && ! scalar($cust_pkg->cust_pkg_discount_active)
+% )
+% {
+% $br=1;
+ (&nbsp;<%pkg_discount_link($cust_pkg)%>&nbsp;)
+% }
+%
+% if ( $curuser->access_right('Customize customer package') ) {
+% $br=1;
+ (&nbsp;<%pkg_customize_link($cust_pkg,$part_pkg)%>&nbsp;)
+% }
+%
+ <% $br ? '<BR>' : '' %>
+% }
+
+% if ( $cust_pkg->num_cust_event
+% && ( $curuser->access_right('Billing event reports')
+% || $curuser->access_right('View customer billing events')
+% )
+% ) {
+ (&nbsp;<%pkg_event_link($cust_pkg)%>&nbsp;)
+% }
+
+ </FONT>
+ </TD>
+ </TR>
+
+% my $editi = $curuser->access_right('Edit customer package invoice details');
+% my $editc = $curuser->access_right('Edit customer package comments');
+% my @cust_pkg_detail = $cust_pkg->cust_pkg_detail;
+% my @invoice_detail = grep { $_->detailtype eq 'I' } @cust_pkg_detail;
+% my @comments = grep { $_->detailtype eq 'C' } @cust_pkg_detail;
+%
+% if ( scalar(@invoice_detail) || scalar(@comments) || $editi || $editc ) {
+%
+% my $editlink = $p. 'edit/cust_pkg_detail?pkgnum='. $cust_pkg->pkgnum.
+% ';detailtype=';
+
+ <TR>
+
+% if ( @invoice_detail ) {
+ <TD VALIGN="top">
+ <% include('/elements/table-grid.html') %>
+ <TR>
+ <TH BGCOLOR="#dddddd" STYLE="border-bottom: dashed 1px black; padding-bottom: 1px">
+ <FONT SIZE="-1">
+ Invoice details
+% if ( $editi && ! $cust_pkg->get('cancel') ) {
+ (<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'I',
+ 'label' => 'edit',
+ 'actionlabel' => 'Edit invoice details',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>)
+% }
+ </FONT>
+ </TH>
+ </TR>
+% foreach my $cust_pkg_detail ( @invoice_detail ) {
+ <TR>
+ <TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
+ </TR>
+% }
+ </TABLE>
+ </TD>
+% } else {
+ <TD>
+% if ( $editi && ! $cust_pkg->get('cancel') ) {
+ <FONT SIZE="-1">
+ (&nbsp;<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'I',
+ 'label' => 'Add&nbsp;invoice&nbsp;details',
+ 'actionlabel' => 'Add invoice details',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>&nbsp;)
+ </FONT>
+% }
+ </TD>
+% }
+
+% if ( @comments ) {
+ <TD VALIGN="top">
+ <% include('/elements/table-grid.html') %>
+ <TR>
+ <TH BGCOLOR="#dddddd" STYLE="border-bottom: dashed 1px black; padding-bottom: 1px">
+ <FONT SIZE="-1">
+ Comments
+% if ( $editc ) {
+ (<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'C',
+ 'label' => 'edit',
+ 'actionlabel' => 'Edit comments',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>)
+% }
+ </FONT>
+ </TH>
+ </TR>
+% foreach my $cust_pkg_detail ( @comments ) {
+ <TR>
+ <TD><FONT SIZE="-1">&nbsp;-&nbsp;<% $cust_pkg_detail->detail |h %></FONT></TD>
+ </TR>
+% }
+ </TABLE>
+ </TD>
+% } else {
+ <TD>
+% if ( $editc ) {
+ <FONT SIZE="-1">
+ (&nbsp;<% include('/elements/popup_link.html', {
+ 'action' => $editlink. 'C',
+ 'label' => 'Add&nbsp;comments',
+ 'actionlabel' => 'Add comments',
+ 'color' => '#333399',
+ 'width' => 763,
+ })
+ %>&nbsp;)
+ </FONT>
+% }
+ </TD>
+% }
+
+ </TR>
+% }
+
+ </TABLE>
+
+</TD>
+
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#subroutines
+
+#false laziness w/status.html
+sub pkg_link {
+ my($action, $label, $cust_pkg) = @_;
+ return '' unless $cust_pkg;
+ qq!<a href="$p$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!;
+}
+
+sub pkg_change_link {
+ my $cust_pkg = shift;
+ my $locationnum = $cust_pkg->locationnum;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. "misc/change_pkg.cgi?locationnum=$locationnum",
+ 'label' => 'Change&nbsp;package',
+ 'actionlabel' => 'Change',
+ 'cust_pkg' => $cust_pkg,
+ );
+}
+
+sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', 'Edit&nbsp;dates', @_ ); }
+
+sub pkg_discount_link {
+ my $cust_pkg = shift or return '';
+ #my $part_pkg = shift;
+ #my $custnum = $cust_pkg->custnum;
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p.'edit/cust_pkg_discount.html',
+ 'label' => 'Discount',
+ 'actionlabel' => 'Discount',
+ 'cust_pkg' => $cust_pkg,
+ 'width' => 616,
+ );
+}
+
+sub pkg_customize_link {
+ my $cust_pkg = shift or return '';
+ my $part_pkg = shift;
+ my $custnum = $cust_pkg->custnum;
+ qq!<A HREF="${p}edit/part_pkg.cgi?!.
+ "clone=". $part_pkg->pkgpart. ';'.
+ "pkgnum=". $cust_pkg->pkgnum.
+ qq!">Customize</A>!;
+}
+
+sub pkg_event_link {
+ my($cust_pkg) = @_;
+ qq!<a href="${p}search/cust_event.html?pkgnum=!. $cust_pkg->pkgnum. qq!">!.
+ 'View package events'.
+ '</a>';
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html
new file mode 100644
index 0000000..6e30922
--- /dev/null
+++ b/httemplate/view/cust_main/packages/services.html
@@ -0,0 +1,140 @@
+% ###
+% # Services
+% ###
+
+ <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+
+% #foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
+% foreach my $part_svc ( $cust_pkg->part_svc ) {
+
+% #foreach my $service (@{$svcpart->{services}}) {
+% foreach my $cust_svc ( @{ $part_svc->cust_pkg_svc } ) {
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD>
+ <TD STYLE="padding-bottom:0px"><B><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD>
+ <TD><% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" COLSPAN="3" VALIGN="top" STYLE="padding-bottom:1px;padding-top:0px"><FONT SIZE="-2" COLOR="#FFD000">
+
+ <% $cust_svc->overlimit ? "Overlimit: ". time2str('%b %o %Y' . ($opt{'cust_pkg-display_times'} ? ' %l:%M %P' : ''), $cust_svc->overlimit) : '' %>
+ </FONT></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">
+
+% if ( $curuser->access_right('Recharge customer service')
+% && $part_svc->svcdb eq 'svc_acct'
+% && ( $cust_svc->svc_x->seconds ne ''
+% || $cust_svc->svc_x->upbytes ne ''
+% || $cust_svc->svc_x->downbytes ne ''
+% || $cust_svc->svc_x->totalbytes ne ''
+% )
+% ) {
+ (&nbsp;<%svc_recharge_link($cust_svc)%>&nbsp;)
+% }
+ </FONT></TD>
+
+ <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px">
+
+% my $ip_addr = $cust_svc->svc_x->ip_addr;
+
+% if ( $part_svc->svcdb eq 'svc_broadband' ) {
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<% include('/elements/popup_link-ping.html', 'ip'=> $ip_addr ) %>&nbsp;)</FONT>
+
+% }
+
+% my $manage_link = $opt{'svc_broadband-manage_link'};
+% if ( $manage_link && $part_svc->svcdb eq 'svc_broadband' ) {
+% my $svc_manage_link = eval(qq("$manage_link"));
+ <FONT SIZE="-1" STYLE="float:left">(&nbsp;<A HREF="<% $svc_manage_link %>">Manage Device</A>&nbsp;)</FONT>
+
+% }
+
+% if ( $curuser->access_right('Unprovision customer service') ) {
+ <FONT SIZE="-2">(&nbsp;<%svc_unprovision_link($cust_svc)%>&nbsp;)</FONT>
+% }
+
+% if ( $part_svc->svcdb eq 'svc_pbx' && $opt{'maestro-status_test'} ){
+ <FONT SIZE="-2">(&nbsp;<A HREF="<% $p %>misc/maestro-customer_status-test.html?<% $cust_pkg->custnum.'+'.$cust_svc->svcnum %>">Test maestro status</A>&nbsp;)</FONT>
+% }
+
+ </TD>
+ </TR>
+% }
+
+% if ( ! $cust_pkg->get('cancel')
+% && $curuser->access_right('Provision customer service')
+% && $part_svc->num_avail
+% ) {
+
+ <TR>
+ <TD COLSPAN=3 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px">
+ <B><% svc_provision_link($cust_pkg, $part_svc, \%opt, $curuser) %></B>
+ </TD>
+ </TR>
+
+% }
+
+% }
+
+ </TABLE>
+ </TD>
+
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $conf = new FS::Conf;
+
+sub svc_provision_link {
+ my ($cust_pkg, $part_svc, $opt, $curuser) = @_;
+ ( my $svc_nbsp = $part_svc->svc ) =~ s/\s+/&nbsp;/g;
+ my $num_avail = $part_svc->num_avail;
+ my $pkgnum_svcpart = "pkgnum=". $cust_pkg->pkgnum. ';'.
+ "svcpart=". $part_svc->svcpart;
+ my $url;
+ if ( $part_svc->svcdb eq 'svc_external' #could be generalized
+ && $opt->{'svc_external-skip_manual'}
+ ) {
+ $url = "${p}edit/process/". $part_svc->svcdb. ".cgi?$pkgnum_svcpart";
+ } else {
+ $url = svc_url(
+ 'm' => $m,
+ 'action' => 'edit',
+ 'part_svc' => $part_svc,
+ 'query' => $pkgnum_svcpart,
+ );
+ #$url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
+ }
+
+ my $link = qq!<A CLASS="provision" HREF="$url">!.
+ "Provision&nbsp;$svc_nbsp&nbsp;($num_avail)</A>";
+ if ( $opt->{'legacy_link'}
+ && $curuser->access_right('View/link unlinked services')
+ )
+ {
+ $link .= '<BR>'.
+ qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
+ qq!$pkgnum_svcpart">!.
+ "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_avail)</A>";
+ }
+ $link;
+}
+
+sub svc_unprovision_link {
+ my $cust_svc = shift or return '';
+ qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?!. $cust_svc->svcnum.
+ qq!', 'Permanently unprovision and delete this service?')">Unprovision</A>!;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
new file mode 100644
index 0000000..a686843
--- /dev/null
+++ b/httemplate/view/cust_main/packages/status.html
@@ -0,0 +1,484 @@
+<TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+ <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+
+%#this should use cust_pkg->status and cust_pkg->statuscolor eventually
+
+% if ( $cust_pkg->get('cancel') ) { #status: cancelled
+% my $cpr = $cust_pkg->last_cust_pkg_reason('cancel');
+
+ <% pkg_status_row($cust_pkg, 'Cancelled', 'cancel', 'color'=>'FF0000', %opt ) %>
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
+ 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+
+% unless ( $cust_pkg->get('setup') ) {
+
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt, ) %>
+
+% } else {
+
+ <% pkg_status_row( $cust_pkg, 'Setup', 'setup', %opt ) %>
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, 'Suspended', 'susp', %opt, curuser=>$curuser ) %>
+
+% }
+%
+% } else {
+%
+% if ( $cust_pkg->get('susp') ) { #status: suspended
+% my $cpr = $cust_pkg->last_cust_pkg_reason('susp');
+
+ <% pkg_status_row( $cust_pkg, 'Suspended', 'susp', 'color'=>'FF9900', %opt ) %>
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '',
+ 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan,
+ %opt,
+ )
+ %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+% unless ( $cust_pkg->get('setup') ) {
+ <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt ) %>
+% } else {
+ <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt ) %>
+% }
+
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+% # pkg_status_row($cust_pkg, 'Next bill', 'bill', %opt)
+ <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire', %opt, curuser=>$curuser ) %>
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Unsuspend customer package') ) {
+ (&nbsp;<% pkg_unsuspend_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+
+% } else { #status: active
+%
+% unless ( $cust_pkg->get('setup') ) { #not setup
+%
+% unless ( $part_pkg->freq ) {
+
+ <% pkg_status_row_colspan( $cust_pkg, 'Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_if(
+ $cust_pkg,
+ ( $part_pkg->freq ? 'Start billing' : 'Bill on' ),
+ 'start_date',
+ %opt
+ )
+ %>
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+ </FONT>
+ </TD>
+ </TR>
+
+% } else {
+
+ <% pkg_status_row_colspan($cust_pkg, "Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %>
+
+% }
+%
+% } else { #setup
+%
+% unless ( $part_pkg->freq ) {
+
+ <% pkg_status_row_colspan($cust_pkg, 'One-time&nbsp;charge', '', 'colspan'=>$colspan, %opt ) %>
+
+ <% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %>
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+% } else {
+%
+% if (scalar($cust_pkg->overlimit)) {
+
+ <% pkg_status_row_colspan( $cust_pkg,
+ 'Overlimit',
+ $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+ 'color'=>'FFD000', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+
+% } else {
+ <% pkg_status_row_colspan( $cust_pkg,
+ 'Active',
+ $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+ 'color'=>'00CC00', 'colspan'=>$colspan,
+ %opt
+ )
+ %>
+% }
+
+ <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+
+ <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt) %>
+
+% }
+%
+% }
+%
+% if ( $opt{'cust_pkg-show_autosuspend'} ) {
+% my $autosuspend = pkg_autosuspend_time( $cust_pkg );
+% $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend;
+% }
+
+ <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %>
+ <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if($cust_pkg, 'Will automatically suspend by', 'autosuspend', %opt) %>
+ <% pkg_status_row_if( $cust_pkg, 'Will suspend on', 'adjourn', %opt, curuser=>$curuser ) %>
+ <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire', %opt, curuser=>$curuser ) %>
+
+% if ( $part_pkg->freq ) {
+
+ <TR>
+ <TD COLSPAN=<%$colspan%>>
+ <FONT SIZE=-1>
+% if ( $curuser->access_right('Suspend customer package') ) {
+ (&nbsp;<% pkg_suspend_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Suspend customer package later') ) {
+ (&nbsp;<% pkg_adjourn_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Delay suspension events') ) {
+ (&nbsp;<% pkg_delay_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package immediately') ) {
+ (&nbsp;<% pkg_cancel_link($cust_pkg) %>&nbsp;)
+% }
+% if ( $curuser->access_right('Cancel customer package later') ) {
+ (&nbsp;<% pkg_expire_link($cust_pkg) %>&nbsp;)
+% }
+
+ <FONT>
+ </TD>
+ </TR>
+% }
+%
+% }
+% }
+
+ </TABLE>
+</TD>
+<%init>
+
+my %opt = @_;
+
+my $bgcolor = $opt{'bgcolor'};
+my $cust_pkg = $opt{'cust_pkg'};
+my $part_pkg = $opt{'part_pkg'};
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $colspan = $opt{'cust_pkg-display_times'} ? 8 : 4;
+my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%';
+
+#false laziness w/edit/REAL_cust_pkg.cgi
+my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until );
+unless ( $part_pkg->is_prepaid ) {
+ $billed_or_prepaid = 'billed';
+ $last_bill_or_renewed = 'Last&nbsp;bill';
+ $next_bill_or_prepaid_until = 'Next&nbsp;bill';
+} else {
+ $billed_or_prepaid = 'prepaid';
+ $last_bill_or_renewed = 'Renewed';
+ $next_bill_or_prepaid_until = 'Prepaid&nbsp;until';
+}
+
+#subroutines
+
+sub myfreq {
+ my $part_pkg = shift;
+ my $freq = $part_pkg->freq_pretty;
+ $freq =~ s/ /&nbsp;/g;
+ $freq;
+}
+
+#false laziness w/package.html
+sub pkg_link {
+ my($action, $label, $cust_pkg) = @_;
+ return '' unless $cust_pkg;
+ qq!<a href="$p$action.cgi?!. $cust_pkg->pkgnum. qq!">$label</a>!;
+}
+
+sub pkg_status_row {
+ my( $cust_pkg, $title, $field, %opt ) = @_;
+
+ my $color = $opt{'color'};
+
+ my $html = qq(<TR><TD WIDTH="<%$width%>" ALIGN="right">);
+ $html .= qq(<FONT COLOR="#$color"><B>) if length($color);
+ $html .= qq($title&nbsp;);
+ $html .= qq(</B></FONT>) if length($color);
+
+ if ( $opt{'pkg_balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
+ $html .= qq(</TD>);
+ $html .= pkg_datestr($cust_pkg, $field, %opt). '</TR>';
+
+ $html;
+}
+
+sub pkg_status_row_if {
+ my( $cust_pkg, $title, $field, %opt ) = @_;
+
+ $title = '<FONT SIZE=-1>(&nbsp;'. pkg_unadjourn_link($cust_pkg). '&nbsp;)&nbsp;</FONT>'. $title
+ if ( $field eq 'adjourn' &&
+ $opt{curuser}->access_right('Suspend customer package later')
+ );
+
+ $title = '<FONT SIZE=-1>(&nbsp;'. pkg_unexpire_link($cust_pkg). '&nbsp;)&nbsp;</FONT>'. $title
+ if ( $field eq 'expire' &&
+ $opt{curuser}->access_right('Cancel customer package later')
+ );
+
+ $cust_pkg->get($field) ? pkg_status_row($cust_pkg, $title, $field, %opt) : '';
+}
+
+sub pkg_status_row_changed {
+ my( $cust_pkg, %opt ) = @_;
+
+ return '' unless $cust_pkg->change_date;
+
+ my $html =
+ pkg_status_row( $cust_pkg, 'Package&nbsp;changed', 'change_date', %opt );
+
+ my $old = $cust_pkg->old_cust_pkg;
+ if ( $old ) {
+ my $part_pkg = $old->part_pkg;
+ my $label = 'Changed from '. $cust_pkg->change_pkgnum. ': '.
+ $part_pkg->pkg_comment(nopartpkg => 1);
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
+ 'size' => '-1',
+ 'align' => 'right',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+ }
+
+ $html;
+}
+
+sub pkg_status_row_noauto {
+ my( $cust_pkg, %opt ) = @_;
+ my $part_pkg = $opt{'part_pkg'};
+ return '' unless $cust_pkg->no_auto || $part_pkg->no_auto;
+
+ #inefficient, should be passed in
+ my $cust_main = $cust_pkg->cust_main;
+
+ return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/;
+ my $what = lc(FS::payby->shortname($cust_main->payby));
+
+ pkg_status_row_colspan( $cust_pkg, "No automatic $what charge", '',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+}
+
+sub pkg_status_row_discount {
+ my( $cust_pkg, %opt ) = @_;
+
+ my $html;
+
+ foreach my $cust_pkg_discount ( $cust_pkg->cust_pkg_discount_active ) {
+
+ my $discount = $cust_pkg_discount->discount;
+
+ my $label = '<B>Discount</B>: '. $discount->description;
+ if ( $discount->months ) {
+ my $remaining = $discount->months - $cust_pkg_discount->months_used;
+ $remaining = sprintf('%.2f', $remaining) if $remaining =~ /\./;
+ $label .= " ($remaining months remaining)"
+ }
+
+ $label .= ' <FONT SIZE="-1">('.
+ '<A HREF="../misc/delete-cust_pkg_discount.html?'.
+ $cust_pkg_discount->pkgdiscountnum.
+ '">remove&nbsp;discount</A>)</FONT>';
+
+ $html .= pkg_status_row_colspan( $cust_pkg, $label, '',
+ 'colspan' => $opt{'colspan'},
+ #%opt,
+ );
+
+ }
+
+ $html;
+}
+
+sub pkg_status_row_colspan {
+ my($cust_pkg, $title, $addl, %opt) = @_;
+
+ my $colspan = $opt{'colspan'};
+
+ my $align = $opt{'align'} ? 'ALIGN="'. $opt{'align'}.'"' : '';
+ my $color = $opt{'color'} ? 'COLOR="#'.$opt{'color'}.'"' : '';
+ my $size = $opt{'size'} ? 'SIZE="'. $opt{'size'}. '"' : '';
+
+ my $html = qq(<TR><TD COLSPAN=$colspan $align>);
+ $html .= qq(<FONT $color $size>) if length($color) || $size;
+ $html .= qq(<B>) if $color && !$size;
+ $html .= $title;
+ $html .= qq(</B>) if $color && !$size;
+ $html .= qq(</FONT>) if length($color) || $size;
+ $html .= ",&nbsp;$addl" if length($addl);
+
+ if ( $opt{'pkg-balances'} && ! $cust_pkg->{_printed_balance}++ ) { #kludge
+ $html .= ' (Balance:&nbsp;<B>'. $opt{'money_char'}.
+ $cust_pkg->cust_main->balance_pkgnum($cust_pkg->pkgnum).
+ '</B>)';
+ }
+
+ $html .= qq(</TD></TR>);
+
+ $html;
+
+}
+
+sub pkg_datestr {
+ my($cust_pkg, $field, %opt) = @_ or return '';
+ return '&nbsp;' unless $cust_pkg->get($field);
+ my $format = '<TD align="left"><B>%b</B></TD>'.
+ '<TD align="right"><B>&nbsp;%o,</B></TD>'.
+ '<TD align="right"><B>&nbsp;%Y</B></TD>';
+ #$format .= '&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
+ $format .= '<TD ALIGN="right"><B>&nbsp;%l</TD>'.
+ '<TD ALIGN="center"><B>:</B></TD>'.
+ '<TD ALIGN="left"><B>%M</B></TD>'.
+ '<TD ALIGN="left"><B>&nbsp;%P</B></TD>'
+ if $opt{'cust_pkg-display_times'};
+ my $strip = time2str($format, $cust_pkg->get($field) );
+ $strip =~ s/ (\d)/$1/g;
+ $strip;
+}
+
+sub pkg_suspend_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=suspend',
+ 'label' => 'Suspend&nbsp;now',
+ 'actionlabel' => 'Suspend',
+ 'color' => '#FF9900',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_adjourn_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=adjourn',
+ 'label' => 'Suspend&nbsp;later',
+ 'actionlabel' => 'Adjourn',
+ 'color' => '#CC6600',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_delay_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/delay_susp_pkg.html',
+ 'label' => 'Delay&nbsp;suspend',
+ 'actionlabel' => 'Delay suspend for',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg', 'Unsuspend', @_ ); }
+sub pkg_unadjourn_link { pkg_link('misc/unadjourn_pkg', 'Abort', @_ ); }
+sub pkg_unexpire_link { pkg_link('misc/unexpire_pkg', 'Abort', @_ ); }
+
+sub pkg_cancel_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=cancel',
+ 'label' => 'Cancel&nbsp;now',
+ 'actionlabel' => 'Cancel',
+ 'color' => '#ff0000',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub pkg_expire_link {
+ include( '/elements/popup_link-cust_pkg.html',
+ 'action' => $p. 'misc/cancel_pkg.html?method=expire',
+ 'label' => 'Cancel&nbsp;later',
+ 'actionlabel' => 'Expire', #"Cancel package $num later"
+ 'color' => '#CC0000',
+ 'cust_pkg' => shift,
+ )
+}
+
+sub svc_recharge_link {
+ include( '/elements/popup_link-cust_svc.html',
+ 'action' => $p. 'misc/recharge_svc.html',
+ 'label' => 'Recharge',
+ 'actionlabel' => 'Recharge',
+ 'color' => '#333399',
+ 'cust_svc' => shift,
+ )
+}
+
+sub pkg_autosuspend_time {
+ my $cust_pkg = shift or return '';
+ my $days = 7;
+ my $time = time;
+ my $pending_suspend = 0;
+ #this seems to be extremely inefficient... and is slowing down all customer
+ #views
+ while ( $days > 0 &&
+ scalar(
+ grep { $_->part_event->action eq 'suspend' }
+ @{$cust_pkg->cust_main->due_cust_event( time => $time + 86400*$days,
+ testonly => 1,
+ ) }
+ )
+ )
+ {
+ $pending_suspend = 1;
+ $days--;
+ }
+
+ $pending_suspend ? time + ($days + 1) * 86400 : '';
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
new file mode 100644
index 0000000..0dc4c41
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history.html
@@ -0,0 +1,445 @@
+%# payment links
+
+% my $s = 0;
+% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter check payment',
+ 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=BILL",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter check payment',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter cash payment',
+ 'action' => "${p}edit/cust_pay.cgi?popup=1;payby=CASH",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter cash payment',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>">Enter Western Union payment</A>
+% }
+
+% if ( ( $payby{'CARD'} || $payby{'DCRD'} )
+% && $curuser->access_right('Process payment')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card payment</A>
+% }
+
+% if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
+% && $curuser->access_right('Process payment')
+% && ! $cust_main->is_encrypted($cust_main->payinfo)
+% ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) payment</A>
+% }
+
+% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline/POS) credit card payment</A>
+% }
+
+<BR>
+
+%# credit link
+
+% if ( $curuser->access_right('Post credit') ) {
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter credit',
+ 'action' => "${p}edit/cust_credit.cgi",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter credit',
+ 'width' => 616, #make room for reasons #540 default
+ #default# 'height' => 336,
+ )
+ %>
+ <BR>
+% }
+
+%# refund links
+
+% $s = 0;
+% if ( $payby{'BILL'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter check refund',
+ 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=BILL",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter check refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+% if ( $payby{'CASH'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <% include('/elements/popup_link-cust_main.html',
+ 'label' => 'Enter cash refund',
+ 'action' => "${p}edit/cust_refund.cgi?popup=1;payby=CASH",
+ 'cust_main' => $cust_main,
+ 'actionlabel' => 'Enter cash refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ )
+ %>
+% }
+
+%# someday, perhaps. very few gateways let you do unlinked refunds at all.
+%# Authorize.net makes you sign a special form
+%#
+%# % if ( ( $payby{'CARD'} || $payby{'DCRD'} )
+%# % && $curuser->access_right('Process refund')
+%# % && ! $cust_main->is_encrypted($cust_main->payinfo)
+%# % ) {
+%# <% $s++ ? ' | ' : '' %>
+%# <A HREF="<% $p %>misc/refund.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card refund</A>
+%# % }
+%#
+%# % if ( ( $payby{'CHEK'} || $payby{'DCHK'} )
+%# % && $curuser->access_right('Process refund')
+%# % && ! $cust_main->is_encrypted($cust_main->payinfo)
+%# % ) {
+%# <% $s++ ? ' | ' : '' %>
+%# <A HREF="<% $p %>misc/refund.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) refund</A>
+%# % }
+
+% if ( $payby{'MCRD'} && $curuser->access_right('Post refund') ) {
+ <% $s++ ? ' | ' : '' %>
+ <A HREF="<% $p %>edit/cust_refund.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline/POS) credit card refund</A>
+% }
+
+<BR>
+
+%# tax exemption link
+
+% my $view_exemptions = $curuser->access_right('View customer tax exemptions');
+% my $add_adjustment = ( $conf->exists('enable_tax_adjustments')
+% && $curuser->access_right('Add customer tax adjustment')
+% );
+% if ( $view_exemptions || $add_adjustment ) {
+
+% if ( $view_exemptions ) {
+ <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A>
+ <% $add_adjustment ? '|' : '' %>
+% }
+
+% if ( $add_adjustment ) {
+ <% include('/elements/popup_link.html', {
+ 'action' => $p.'edit/cust_tax_adjustment.html?custnum='. $cust_main->custnum,
+ 'label' => 'Add tax adjustment',
+ 'actionlabel' => 'Add tax adjustment',
+ #'color' => '#333399',
+ #'width' => 763,
+ 'height' => 200,
+ })
+ %>
+ |
+ <A HREF="<% $p %>search/cust_tax_adjustment.html?custnum=<% $custnum %>">View tax adjustments</A>
+% }
+
+ <BR>
+% }
+
+%# batched payment links
+
+% if ( ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
+% && $curuser->access_right('View customer batched payments')
+% )
+% {
+ View batched payments:
+% foreach my $status (qw( Queued In-transit Complete All )) {
+ <A HREF="<% $p %>search/cust_pay_batch.cgi?status=<% $status{$status} %>;custnum=<% $custnum %>"><% $status %></A>
+ <% $status ne 'All' ? '|' : '' %>
+% }
+ <BR>
+% }
+
+%# pending payment links
+
+% if ( $curuser->access_right('View customer pending payments')
+% && scalar($cust_main->cust_pay_pending)
+% )
+% {
+ <A HREF="<% $p %>search/cust_pay_pending.html?magic=_date;statusNOT=done;custnum=<% $custnum %>">View pending payments</A><BR>
+% }
+
+%# and now the table
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Refund</FONT></TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Balance</FONT></TH>
+</TR>
+
+%#display payment history
+
+%my $money_char = $conf->config('money_char') || '$';
+%
+%sub balance_forward_row {
+% my( $b, $date, $money_char ) = @_;
+% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
+
+ <TR ID="balance_forward_row">
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <% time2str($date_format, $date) %>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#dddddd">
+ <I>Starting balance on <% time2str($date_format, $date) %></I>
+ (<A HREF="javascript:void(0);" onClick="show_history();">show prior history</A>)
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd"></TD>
+ <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD>
+
+ </TR>
+%}
+%
+%my $balance = 0;
+%my %target = ();
+%
+%my $years = $conf->config('payment_history-years') || 2;
+%my $older_than = time - $years * 31556926; #60*60*24*365.2422
+%my $hidden = 0;
+%my $seen = 0;
+%my $old_history = 0;
+%my $lastdate = 0;
+%
+%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
+%
+% $lastdate = $item->{'date'};
+%
+% my $display;
+% if ( $item->{'date'} < $older_than ) {
+% $display = ' STYLE="display:none" ';
+% $hidden = 1;
+% } else {
+%
+% $display = '';
+%
+% if ( $hidden && ! $seen++ ) {
+% balance_forward_row($balance, $item->{'date'}, $money_char);
+% }
+%
+% }
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% my $charge = exists($item->{'charge'})
+% ? sprintf("$money_char\%.2f", $item->{'charge'})
+% : '';
+%
+% my $payment = exists($item->{'payment'})
+% ? sprintf("-&nbsp;$money_char\%.2f", $item->{'payment'})
+% : '';
+%
+% $payment ||= sprintf( "<DEL>-&nbsp;$money_char\%.2f</DEL>",
+% $item->{'void_payment'}
+% )
+% if exists($item->{'void_payment'});
+%
+% my $credit = exists($item->{'credit'})
+% ? sprintf("-&nbsp;$money_char\%.2f", $item->{'credit'})
+% : '';
+%
+% my $refund = exists($item->{'refund'})
+% ? sprintf("$money_char\%.2f", $item->{'refund'})
+% : '';
+%
+% my $target = exists($item->{'target'}) ? $item->{'target'} : '';
+%
+% $balance += $item->{'charge'} if exists $item->{'charge'};
+% $balance -= $item->{'payment'} if exists $item->{'payment'};
+% $balance -= $item->{'credit'} if exists $item->{'credit'};
+% $balance += $item->{'refund'} if exists $item->{'refund'};
+% $balance = sprintf("%.2f", $balance);
+% $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+% ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/-&nbsp;\$/;
+%
+%
+
+
+ <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+% unless ( !$target || $target{$target}++ ) {
+
+ <A NAME="<% $target %>">
+% }
+
+ <% time2str($date_format, $item->{'date'}) %>
+% if ( $target && $target{$target} == 1 ) {
+
+ </A>
+% }
+
+ </FONT>
+ </TD>
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $item->{'desc'} %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $charge %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $payment %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $credit %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $refund %>
+ </TD>
+ <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $showbalance %>
+ </TD>
+ </TR>
+% }
+
+%if ( scalar(@history) && $hidden && ! $seen++ ) {
+% balance_forward_row($balance, $lastdate, $money_char);
+%}
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+
+function show_history () {
+ //alert('showing history!');
+
+ var balance_forward_row = document.getElementById('balance_forward_row');
+
+ balance_forward_row.style.display = 'none';
+ for ( var i = 0; i < <% $old_history %>; i++ ) {
+ var oldRow = document.getElementById('old_history'+i);
+ oldRow.style.display = '';
+ }
+
+}
+
+</SCRIPT>
+
+<%init>
+
+my( $cust_main ) = @_;
+my $custnum = $cust_main->custnum;
+
+my $conf = new FS::Conf;
+my $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my %status = (
+ 'Queued' => 'O', #Open
+ 'In-transit' => 'I',
+ 'Complete' => 'R', #Resolved
+ 'All' => '',
+);
+
+#get payment history
+my @history = ();
+
+my %opt = (
+ ( map { $_ => scalar($conf->config($_)) }
+ qw( card_refund-days date_format )
+ ),
+ ( map { $_ => $conf->exists($_) }
+ qw( deleteinvoices deletepayments deleterefunds pkg-balances )
+ )
+);
+
+#invoices
+foreach my $cust_bill ($cust_main->cust_bill) {
+ push @history, {
+ 'date' => $cust_bill->_date,
+ 'desc' => include('payment_history/invoice.html', $cust_bill, %opt ),
+ 'charge' => $cust_bill->charged,
+ };
+}
+
+#statements
+foreach my $cust_statement ($cust_main->cust_statement) {
+ push @history, {
+ 'date' => $cust_statement->_date,
+ 'desc' => include('payment_history/statement.html', $cust_statement, %opt ),
+ #'charge' => $cust_bill->charged,
+ };
+}
+
+#payments (some false laziness w/credits)
+foreach my $cust_pay ($cust_main->cust_pay) {
+ push @history, {
+ 'date' => $cust_pay->_date,
+ 'desc' => include('payment_history/payment.html', $cust_pay, %opt ),
+ 'payment' => $cust_pay->paid,
+ #'target' => $target, #XXX
+ };
+}
+
+#voided payments
+foreach my $cust_pay_void ($cust_main->cust_pay_void) {
+ push @history, {
+ 'date' => $cust_pay_void->_date,
+ 'desc' => include('payment_history/voided_payment.html', $cust_pay_void, %opt ),
+ 'void_payment' => $cust_pay_void->paid,
+ };
+
+}
+
+#credits (some false laziness w/payments)
+foreach my $cust_credit ($cust_main->cust_credit) {
+ push @history, {
+ 'date' => $cust_credit->_date,
+ 'desc' => include('payment_history/credit.html', $cust_credit, %opt ),
+ 'credit' => $cust_credit->amount,
+ };
+
+}
+
+#refunds
+foreach my $cust_refund ($cust_main->cust_refund) {
+ push @history, {
+ 'date' => $cust_refund->_date,
+ 'desc' => include('payment_history/refund.html', $cust_refund),
+ 'refund' => $cust_refund->refund,
+ };
+
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/credit.html b/httemplate/view/cust_main/payment_history/credit.html
new file mode 100644
index 0000000..75038cc
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/credit.html
@@ -0,0 +1,156 @@
+<% $pre %>Credit<% $post %>
+by <% $cust_credit->otaker %><% "$reason$desc$apply$delete$unapply" %>
+<%init>
+
+my( $cust_credit, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my @cust_credit_bill = $cust_credit->cust_credit_bill;
+my @cust_credit_refund = $cust_credit->cust_credit_refund;
+
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_credit->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_credit->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my %cust_credit_bill_width = ('width' => 392);
+my %cust_credit_bill_height = ();
+if ($conf->exists('cust_credit_bill_pkg-manual')) {
+ %cust_credit_bill_width = ('width' => 592);
+ %cust_credit_bill_height = ('height' => 436);
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
+if ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ if ( $curuser->access_right('Apply credit') ) {
+ if ( $cust_credit->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_credit_bill.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit',
+ %cust_credit_bill_width,
+ %cust_credit_bill_height,
+ ).
+ ')';
+ }
+ if ( $cust_credit->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_credit_refund.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+} elsif ( scalar(@cust_credit_bill) == 1
+ && scalar(@cust_credit_refund) == 0
+ && $cust_credit->credited == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc .= ' '. $cust_credit_bill[0]->applied_to_invoice;
+} elsif ( scalar(@cust_credit_bill) == 0
+ && scalar(@cust_credit_refund) == 1
+ && $cust_credit->credited == 0 ) {
+ #applied to one refund
+ $desc .= ' refunded on '. time2str($date_format, $cust_credit_refund[0]->_date);
+} else {
+ #complicated
+ $desc .= '<BR>';
+ foreach my $app ( sort { $a->_date <=> $b->_date }
+ ( @cust_credit_bill, @cust_credit_refund ) ) {
+ if ( $app->isa('FS::cust_credit_bill') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' '. $app->applied_to_invoice.
+ '<BR>';
+ #' on '. time2str($date_format, $app->_date).
+ } elsif ( $app->isa('FS::cust_credit_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on '. time2str($date_format, $app->_date).
+ '<BR>';
+ } else {
+ die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
+ }
+ }
+ if ( $cust_credit->credited > 0 ) {
+ $desc .= '&nbsp;&nbsp;<B><FONT COLOR="#FF0000">$'.
+ $cust_credit->credited. ' unapplied</FONT></B>';
+ if ( $curuser->access_right('Apply credit') ) {
+ if ( $cust_credit->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_credit_bill.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit',
+ %cust_credit_bill_width,
+ %cust_credit_bill_height,
+ ).
+ ')';
+ }
+ if ( $cust_credit->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_credit_refund.cgi?".
+ $cust_credit->crednum,
+ 'actionlabel' => 'Apply credit to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+ $desc .= '<BR>';
+ }
+}
+#
+my $delete = '';
+if ( $cust_credit->closed !~ /^Y/i
+
+ #s'pose deleting a credit isn't bad like deleting a payment
+ # and this needs to be generally available until we have credit voiding..
+ #&& $conf->exists('deletecredits')
+
+ && $curuser->access_right('Delete credit')
+ )
+{
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum.
+ qq!', 'Are you sure you want to delete this credit?')">!.
+ qq!delete</A>)!;
+}
+
+my $unapply = '';
+if ( $cust_credit->closed !~ /^Y/i
+ && scalar(@cust_credit_bill)
+ && $curuser->access_right('Unapply credit')
+ )
+{
+ $unapply = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum.
+ qq!', 'Are you sure you want to unapply this credit?')">!.
+ qq!unapply</A>)!;
+}
+
+my $reason = $cust_credit->reason
+ ? ' ('. $cust_credit->reason. ')'
+ : '';
+
+</%init>
+
diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html
new file mode 100644
index 0000000..c0d32df
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/invoice.html
@@ -0,0 +1,45 @@
+<% $link %><% $pre %>Invoice #<% $cust_bill->display_invnum %>
+(Balance $ <% $cust_bill->owed %>)<% $post %><% $link ? '</A>' : '' %><% $delete %><% $events %>
+<%init>
+
+my( $cust_bill, %opt ) = @_;
+
+my $conf = new FS::Conf;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($pre, $post) = ('', '');
+if ( $cust_bill->owed > 0 ) {
+ $pre = '<B><FONT SIZE="+1" COLOR="#FF0000">Open ';
+ $post = '</FONT></B>';
+}
+
+my $invnum = $cust_bill->invnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
+ : '';
+
+my $delete = '';
+if ( $opt{'deleteinvoices'} && $curuser->access_right('Delete invoices') ) {
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_bill.html?$invnum',!.
+ qq!'Are you sure you want to delete this invoice?')"!.
+ qq! TITLE="Delete this invoice from the database completely"!.
+ qq!>delete</A>)!;
+}
+
+my $events = '';
+#1.9
+if ( $cust_bill->num_cust_event
+ && ( $curuser->access_right('Billing event reports')
+ || $curuser->access_right('View customer billing events')
+ )
+ ) {
+ $events =
+ qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=$invnum!.
+ '">(&nbsp;View invoice events&nbsp;)</A></FONT>';
+}
+#
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
new file mode 100644
index 0000000..6ec9fdb
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -0,0 +1,225 @@
+<% $pre %>Payment<% $post %> by <% $otaker %>
+<% "$info$desc$view$apply$refund$void$delete$unapply" %>
+<%init>
+
+my( $cust_pay, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_pay->payby;
+
+my $payinfo;
+if ( $payby eq 'CARD' ) {
+ $payinfo = $cust_pay->paymask;
+} elsif ( $payby eq 'CHEK' ) {
+ my( $account, $aba ) = split('@', $cust_pay->paymask );
+ $payinfo = "ABA $aba, Acct #$account";
+} else {
+ $payinfo = $cust_pay->payinfo;
+}
+my @cust_bill_pay = $cust_pay->cust_bill_pay;
+my @cust_pay_refund = $cust_pay->cust_pay_refund;
+
+my $target = "$payby$payinfo";
+$payby =~ s/^BILL$/Check #/ if $payinfo;
+$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^PREP$/Prepaid card /;
+$payby =~ s/^CARD$/Credit card #/;
+$payby =~ s/^COMP$/Complimentary by /;
+$payby =~ s/^CASH$/Cash/;
+$payby =~ s/^WEST$/Western Union/;
+$payby =~ s/^MCRD$/Manual credit card/;
+$payby =~ s/^BILL$//;
+my $info = $payby ? "($payby$payinfo)" : '';
+
+my $desc = '';
+if ( $opt{'pkg-balances'} && $cust_pay->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ $desc .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my %cust_bill_pay_width = ('width' => 392);
+my %cust_bill_pay_height = ();
+if ($conf->exists('cust_bill_pay_pkg-manual')) {
+ %cust_bill_pay_width = ('width' => 592);
+ %cust_bill_pay_height = ('height' => 436);
+}
+
+my( $pre, $post, $apply, $ext ) = ( '', '', '', '' );
+if ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 0 ) {
+ #completely unapplied
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+ if ( $curuser->access_right('Apply payment') ) {
+ if ( $cust_pay->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_bill_pay.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment',
+ %cust_bill_pay_width,
+ %cust_bill_pay_height,
+ ).
+ ')';
+ }
+ if ( $cust_pay->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_pay_refund.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+} elsif ( scalar(@cust_bill_pay) == 1
+ && scalar(@cust_pay_refund) == 0
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one invoice, the usual situation
+ $desc .= ' '. $cust_bill_pay[0]->applied_to_invoice;
+} elsif ( scalar(@cust_bill_pay) == 0
+ && scalar(@cust_pay_refund) == 1
+ && $cust_pay->unapplied == 0 ) {
+ #applied to one refund
+ $desc .= ' refunded on '. time2str($date_format, $cust_pay_refund[0]->_date);
+} else {
+ #complicated
+ $desc .= '<BR>';
+ foreach my $app ( sort { $a->_date <=> $b->_date }
+ ( @cust_bill_pay, @cust_pay_refund ) ) {
+ if ( $app->isa('FS::cust_bill_pay') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' '. $app->applied_to_invoice.
+ '<BR>';
+ #' on '. time2str($date_format, $cust_bill_pay->_date).
+ } elsif ( $app->isa('FS::cust_pay_refund') ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '$'. $app->amount.
+ ' refunded on '. time2str($date_format, $app->_date).
+ '<BR>';
+ } else {
+ die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
+ }
+ }
+ if ( $cust_pay->unapplied > 0 ) {
+ $desc .= '&nbsp;&nbsp;'.
+ '<B><FONT COLOR="#FF0000">$'.
+ $cust_pay->unapplied. ' unapplied</FONT></B>';
+ if ( $curuser->access_right('Apply payment') ) {
+ if ( $cust_pay->cust_main->total_owed > 0 ) {
+ $apply = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply',
+ 'action' => "${p}edit/cust_bill_pay.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment',
+ %cust_bill_pay_width,
+ %cust_bill_pay_height,
+ ).
+ ')';
+ }
+ if ( $cust_pay->cust_main->total_unapplied_refunds > 0 ) {
+ $apply.= ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => 'apply to refund',
+ 'action' => "${p}edit/cust_pay_refund.cgi?".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Apply payment to refund',
+ 'width' => 392,
+ #default# 'height' => 336,
+ ).
+ ')';
+ }
+ }
+ $desc .= '<BR>';
+ }
+}
+
+my $view =
+ ' ('. include('/elements/popup_link.html',
+ 'label' => 'view receipt',
+ 'action' => "${p}view/cust_pay.html?link=popup;paynum=".
+ $cust_pay->paynum,
+ 'actionlabel' => 'Payment Receipt',
+ ).
+ ')';
+
+my $refund = '';
+my $refund_days = $opt{'card_refund-days'} || 120;
+if ( $cust_pay->closed !~ /^Y/i
+ && $cust_pay->payby =~ /^(CARD|CHEK)$/
+ && time-$cust_pay->_date < $refund_days*86400
+ && $cust_pay->unrefunded > 0
+ && $curuser->access_right('Refund payment')
+) {
+ $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
+ qq!paynum=!. $cust_pay->paynum. '"'.
+ qq! TITLE="Send a refund for this payment to the payment gateway"!.
+ qq!>refund</A>)!;
+}
+
+my $void = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && ( ( $cust_pay->payby eq 'CARD'
+ && $curuser->access_right('Credit card void')
+ )
+ || ( $cust_pay->payby eq 'CHEK'
+ && $curuser->access_right('Echeck void')
+ )
+ || ( $cust_pay->payby !~ /^(CARD|CHEK)$/
+ && $curuser->access_right('Regular void')
+ )
+ )
+ )
+{
+ $void = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to void this payment?')"!.
+ qq! TITLE="Void this payment from the database!.
+ ( $cust_pay->payby =~ /^(CARD|CHEK)$/
+ ? ' (do not send anything to the payment gateway)'
+ : ''
+ ). '"'.
+ qq!>void</A>)!;
+}
+
+my $delete = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && $opt{'deletepayments'}
+ && $curuser->access_right('Delete payment')
+ )
+{
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to delete this payment?')"!.
+ qq! TITLE="Delete this payment from the database completely - not recommended"!.
+ qq!>delete</A>)!;
+}
+
+my $unapply = '';
+if ( $cust_pay->closed !~ /^Y/i
+ && scalar(@cust_bill_pay)
+ && $curuser->access_right('Unapply payment')
+ )
+{
+ $unapply = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum.
+ qq!', 'Are you sure you want to unapply this payment?')"!.
+ qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!.
+ qq!>unapply</A>)!;
+}
+
+my $otaker = $cust_pay->otaker;
+$otaker = '<i>auto billing</i>' if $otaker eq 'fs_daily';
+$otaker = '<i>customer self-service</i>' if $otaker eq 'fs_selfservice';
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/refund.html b/httemplate/view/cust_main/payment_history/refund.html
new file mode 100644
index 0000000..4a48fea
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/refund.html
@@ -0,0 +1,50 @@
+<% $pre %>Refund<% $post %>
+(<% $payby. $payinfo %>)
+by <% $cust_refund->otaker %><% $view %><% $delete %>
+<%init>
+
+my( $cust_refund, %opt ) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_refund->payby;
+my $payinfo = $payby eq 'CARD'
+ ? $cust_refund->paymask
+ : $cust_refund->payinfo;
+
+$payby =~ s/^BILL$/Check #/ if $payinfo;
+$payby =~ s/^BILL$/Check/;
+$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^(CARD|COMP)$/$1 /;
+
+my($pre, $post) = ('', '');
+if ( $cust_refund->unapplied > 0 ) {
+ $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+ $post = '</FONT></B>';
+}
+
+my $view =
+ ' ('. include('/elements/popup_link.html',
+ 'label' => 'view receipt',
+ 'action' => "${p}view/cust_refund.html?link=popup;".
+ 'refundnum='. $cust_refund->refundnum,
+ 'actionlabel' => 'Payment Receipt',
+ ).
+ ')';
+
+
+my $delete = '';
+if ( $cust_refund->closed !~ /^Y/i
+ && $opt{'deleterefunds'}
+ && $curuser->access_right('Delete refund')
+ )
+{
+ $delete = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/delete-cust_refund.cgi?!. $cust_refund->refundnum.
+ qq!', 'Are you sure you want to delete this refund?')"!.
+ qq! TITLE="Delete this refund from the database completely - not recommended"!.
+ qq!>delete</A>)!;
+}
+
+</%init>
+
diff --git a/httemplate/view/cust_main/payment_history/statement.html b/httemplate/view/cust_main/payment_history/statement.html
new file mode 100644
index 0000000..dedec9b
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/statement.html
@@ -0,0 +1,34 @@
+<% $link %><% $pre %>Statement #<% $statementnum %>
+%# (Balance $ <% $cust_statement->owed %>)
+<% $post %><% $link ? '</A>' : '' %><% $events %>
+<%init>
+
+my( $cust_statement, %opt ) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($pre, $post) = ('', '');
+#if ( $cust_statement->owed > 0 ) {
+# $pre = '<B><FONT SIZE="+1" COLOR="#FF0000">Open ';
+# $post = '</FONT></B>';
+#}
+
+my $statementnum = $cust_statement->statementnum;
+
+my $link = $curuser->access_right('View invoices')
+ ? qq!<A HREF="${p}view/cust_statement.html?$statementnum">!
+ : '';
+
+my $events = '';
+
+#if ( $cust_statement->num_cust_event
+# && ( $curuser->access_right('Billing event reports')
+# || $curuser->access_right('View customer billing events')
+# )
+# ) {
+# $events =
+# qq!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?statementnum=!.
+# $cust_statement->statementnum. '">(&nbsp;View statement events&nbsp;)</A></FONT>';
+#}
+
+</%init>
diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html
new file mode 100644
index 0000000..be68ff0
--- /dev/null
+++ b/httemplate/view/cust_main/payment_history/voided_payment.html
@@ -0,0 +1,56 @@
+<DEL>Payment <% $info %></DEL>
+<I>voided <% time2str($date_format, $cust_pay_void->void_date) %>
+by <% $cust_pay_void->otaker %></I><% $unvoid %>
+<%init>
+
+my( $cust_pay_void, %opt ) = @_;
+
+my $date_format = $opt{'date_format'} || '%m/%d/%Y';
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $payby = $cust_pay_void->payby;
+
+my $payinfo;
+if ( $payby eq 'CARD' ) {
+ $payinfo = $cust_pay_void->paymask;
+} elsif ( $payby eq 'CHEK' ) {
+ my( $account, $aba ) = split('@', $cust_pay_void->paymask );
+ $payinfo = "ABA $aba, Acct #$account";
+} else {
+ $payinfo = $cust_pay_void->payinfo;
+}
+
+$payby =~ s/^BILL$/Check #/ if $payinfo;
+$payby =~ s/^CHEK$/Electronic check /;
+$payby =~ s/^PREP$/Prepaid card /;
+$payby =~ s/^CARD$/Credit card #/;
+$payby =~ s/^COMP$/Complimentary by /;
+$payby =~ s/^CASH$/Cash/;
+$payby =~ s/^WEST$/Western Union/;
+$payby =~ s/^MCRD$/Manual credit card/;
+$payby =~ s/^BILL$//;
+my $info = $payby ? "($payby$payinfo)" : '';
+
+if ( $opt{'pkg-balances'} && $cust_pay_void->pkgnum ) {
+ my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay_void->pkgnum } );
+ $info .= ' for '. $cust_pkg->pkg_label_long;
+}
+
+my $unvoid = '';
+if ( $cust_pay_void->closed !~ /^Y/i
+ && $curuser->access_right('Unvoid')
+ )
+{
+ $unvoid = qq! (<A HREF="javascript:areyousure('!.
+ qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum.
+ qq!', 'Are you sure you want to unvoid this payment?')"!.
+ qq! TITLE="Unvoid this payment from the database!.
+ ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/
+ ? ' (do not send anything to the payment gateway)'
+ : ''
+ ). '"'.
+ qq!>unvoid</A>)!;
+}
+
+</%init>
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
new file mode 100644
index 0000000..e90ae52
--- /dev/null
+++ b/httemplate/view/cust_main/tickets.html
@@ -0,0 +1,117 @@
+<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 id="CreateTicketLink" HREF="<% $new_link %>">Create new ticket</A>
+ in queue
+% my %queues = FS::TicketSystem->queues();
+% 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 ) {
+% #should consider whether the user has ACL to create ticket in each queue
+ <OPTION VALUE="<% $queueid %>"
+ <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %>
+ ><% $queues{$queueid} |h %>
+% }
+</SELECT>
+<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT>
+% }
+</FORM>
+<BR>
+
+(<A HREF="<% $open_link %>">View <% $openlabel %> tickets for this customer</A>)
+(<A HREF="<% $res_link %>">View resolved tickets for this customer</A>)
+<BR><BR>
+
+<% include("/elements/table-grid.html") %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">#</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Subject</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Queue</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Owner</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Priority</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 ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <% $ticket->{content}
+ ? $ticket->{content}.' ('.$ticket->{priority}.')'
+ : $ticket->{priority}
+ %>
+ </TD>
+
+ </TR>
+
+% }
+
+</TABLE>
+
+<%init>
+
+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,
+ join(', ', $cust_main->invoicing_list_emailonly ) );
+
+my $new_link = FS::TicketSystem->href_new_ticket(
+ $cust_main,
+ join(', ', $cust_main->invoicing_list_emailonly )
+ );
+
+</%init>
diff --git a/httemplate/view/cust_pay.html b/httemplate/view/cust_pay.html
new file mode 100644
index 0000000..2f23d9e
--- /dev/null
+++ b/httemplate/view/cust_pay.html
@@ -0,0 +1,162 @@
+% if ( $link eq 'popup' ) {
+
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
+
+ <CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
+
+% } elsif ( $link eq 'print' ) {
+
+ <% include('/elements/header-popup.html', "$thing Receipt" ) %>
+
+% #it would be nice if the menubar could be hidden for print, but better to
+% # have it available than not, otherwise the user winds up at a dead end
+ <% menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ %>
+ <BR><BR>
+
+% } else {
+
+ <% include('/elements/header.html', "$thing Receipt", menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Print receipt' => $pr_link,
+ ))
+ %>
+
+% }
+
+% unless ($link eq 'popup' ) {
+ <% include('/elements/small_custview.html',
+ $custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #no balance
+ )
+ %>
+ <BR><BR>
+% }
+
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Payment#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->paynum %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->_date %></B></TD>
+</TR>
+
+% if ( $void ) {
+
+ <TR>
+ <TD ALIGN="right">Void Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_pay->void_date %></B></TD>
+ </TR>
+
+%# <TR>
+%# <TD ALIGN="right">Void reason</TD>
+%# <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->reason %></B></TD>
+%# </TR>
+
+% }
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_pay->paid %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Payment method</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->payby_name %> #<% $cust_pay->paymask %></B></TD>
+</TR>
+
+% if ( $cust_pay->payby =~ /^(CARD|CHEK|LECB)$/ && $cust_pay->paybatch ) {
+
+ <TR>
+ <TD ALIGN="right">Processor</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->processor %></B></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Authorization#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->authorization %></B></TD>
+ </TR>
+
+% if ( $cust_pay->order_number ) {
+ <TR>
+ <TD ALIGN="right">Order#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->order_number %></B></TD>
+ </TR>
+% }
+
+% }
+
+% if ( $conf->exists('pkg-balances') && $cust_pay->pkgnum ) {
+% my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_pay->pkgnum } );
+ <TR>
+ <TD ALIGN="right">For package</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_pkg->pkg_label_long %></B></TD>
+ </TR>
+
+% }
+
+</TABLE>
+
+% if ( $link eq 'print' ) {
+
+ <SCRIPT TYPE="text/javascript">
+ window.print();
+ </SCRIPT>
+
+% }
+
+% if ( $link =~ /^(popup|print)$/ ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices') #remove this in 1.9 EVENTUALLY
+ || $curuser->access_right('View customer payments');
+
+$cgi->param('paynum') =~ /^(\d+)$/ or die "no paynum";
+my $paynum = $1;
+
+my $link = '';
+if ( $cgi->param('link') =~ /^(\w+)$/ ) {
+ $link = $1;
+}
+
+my $void = $cgi->param('void') ? 1 : 0;
+my $thing = $void ? 'Voided Payment' : 'Payment';
+my $table = $void ? 'cust_pay_void' : 'cust_pay';
+
+my $cust_pay = qsearchs({
+ 'select' => "$table.*",
+ 'table' => $table,
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'paynum' => $paynum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "$thing #$paynum not found!" unless $cust_pay;
+
+my $pr_link = "${p}view/cust_pay.html?link=print;paynum=$paynum;void=$void";
+
+my $custnum = $cust_pay->custnum;
+my $display_custnum = $cust_pay->cust_main->display_custnum;
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+tie my %payby, 'Tie::IxHash', FS::payby->payby2longname;
+
+</%init>
diff --git a/httemplate/view/cust_pay_void.html b/httemplate/view/cust_pay_void.html
new file mode 100644
index 0000000..8c22170
--- /dev/null
+++ b/httemplate/view/cust_pay_void.html
@@ -0,0 +1 @@
+<% include('cust_pay.html', @_, 'void' => 1 ) %>
diff --git a/httemplate/view/cust_refund.html b/httemplate/view/cust_refund.html
new file mode 100644
index 0000000..138c878
--- /dev/null
+++ b/httemplate/view/cust_refund.html
@@ -0,0 +1,142 @@
+% if ( $link eq 'popup' ) {
+
+ <% include('/elements/header-popup.html', "Refund Receipt" ) %>
+
+ <CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
+
+% } elsif ( $link eq 'print' ) {
+
+ <% include('/elements/header-popup.html', "Refund Receipt" ) %>
+
+% #it would be nice if the menubar could be hidden for print, but better to
+% # have it available than not, otherwise the user winds up at a dead end
+ <% menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ %>
+ <BR><BR>
+
+% } else {
+
+ <% include('/elements/header.html', "Refund Receipt", menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Print receipt' => $pr_link,
+ ))
+ %>
+
+% }
+
+% unless ($link eq 'popup' ) {
+ <% include('/elements/small_custview.html',
+ $custnum,
+ scalar($conf->config('countrydefault')),
+ 1, #no balance
+ )
+ %>
+ <BR><BR>
+% }
+
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Refund#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->refundnum %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% time2str"%a&nbsp;%b&nbsp;%o,&nbsp;%Y&nbsp;%r", $cust_refund->_date %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $money_char. $cust_refund->refund %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->reason %></B></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Refund method</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->payby_name %><% $cust_refund->paymask ? ' #'.$cust_refund->paymask : '' %></B></TD>
+</TR>
+
+% if ( $cust_refund->payby =~ /^(CARD|CHEK|LECB)$/ && $cust_refund->paybatch ) {
+
+ <TR>
+ <TD ALIGN="right">Processor</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->processor %></B></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Authorization#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->authorization %></B></TD>
+ </TR>
+
+% if ( $cust_refund->order_number ) {
+ <TR>
+ <TD ALIGN="right">Order#</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $cust_refund->order_number %></B></TD>
+ </TR>
+% }
+
+% }
+
+</TABLE>
+
+% if ( $link eq 'print' ) {
+
+ <SCRIPT TYPE="text/javascript">
+ window.print();
+ </SCRIPT>
+
+% }
+
+% if ( $link =~ /^(popup|print)$/ ) {
+ </BODY>
+ </HTML>
+% } else {
+ <% include('/elements/footer.html') %>
+% }
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View invoices') #remove this in 1.9 EVENTUALLY
+ || $curuser->access_right('View customer payments');
+ #'View customer refunds' ???
+
+
+$cgi->param('refundnum') =~ /^(\d+)$/ or die "no refundnum";
+my $refundnum = $1;
+
+my $link = '';
+if ( $cgi->param('link') =~ /^(\w+)$/ ) {
+ $link = $1;
+}
+
+my $cust_refund = qsearchs({
+ 'select' => 'cust_refund.*',
+ 'table' => 'cust_refund',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'refundnum' => $refundnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Refund #$refundnum not found!" unless $cust_refund;
+
+my $pr_link = "${p}view/cust_refund.html?link=print;refundnum=$refundnum";
+
+my $custnum = $cust_refund->custnum;
+my $display_custnum = $cust_refund->cust_main->display_custnum;
+
+my $conf = new FS::Conf;
+
+my $money_char = $conf->config('money_char') || '$';
+
+tie my %payby, 'Tie::IxHash', FS::payby->payby2longname;
+
+</%init>
diff --git a/httemplate/view/cust_statement-pdf.cgi b/httemplate/view/cust_statement-pdf.cgi
new file mode 100755
index 0000000..a1739e0
--- /dev/null
+++ b/httemplate/view/cust_statement-pdf.cgi
@@ -0,0 +1,28 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statementnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $pdf = $cust_statement->print_pdf( '', $templatename);
+
+http_header('Content-Type' => 'application/pdf' );
+http_header('Content-Length' => length($pdf) );
+http_header('Cache-control' => 'max-age=60' );
+
+</%init>
diff --git a/httemplate/view/cust_statement.html b/httemplate/view/cust_statement.html
new file mode 100755
index 0000000..3e1345e
--- /dev/null
+++ b/httemplate/view/cust_statement.html
@@ -0,0 +1,79 @@
+<% include("/elements/header.html",'Statement View', menubar(
+ "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Resend invoices') ) {
+
+%# <A HREF="<% $p %>misc/send-statement.cgi?method=print;<% $link %>">Re-print this statement</A>
+
+% if ( grep { $_ ne 'POST' } $cust_statement->cust_main->invoicing_list ) {
+%# |
+ <A HREF="<% $p %>misc/send-statement.cgi?method=email;<% $link %>">Re-email this statement</A>
+% }
+
+% if ( 0 ) {
+% #if ( $conf->exists('hylafax') && length($cust_statement->cust_main->fax) ) {
+ | <A HREF="<% $p %>misc/send-statement.cgi?method=fax;<% $link %>">Re-fax this statement</A>
+% }
+
+ <BR><BR>
+
+% }
+
+
+% #if ( $conf->exists('invoice_latex') ) {
+% if ( 0 ) { #broken???
+
+ <A HREF="<% $p %>view/cust_statement-pdf.cgi?<% $link %>">View typeset statement</A>
+ <BR><BR>
+% }
+
+% #if ( $cust_statement->num_cust_event ) {
+% if ( 0 ) {
+<A HREF="<%$p%>search/cust_event.html?statementnum=<% $cust_statement->statementnum %>">(&nbsp;View statement events&nbsp;)</A><BR><BR>
+% }
+
+% if ( $conf->exists('invoice_html') ) {
+
+ <% join('', $cust_statement->print_html('', $templatename) ) %>
+% } else {
+
+ <PRE><% join('', $cust_statement->print_text('', $templatename) ) %></PRE>
+% }
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint statement
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2 || 'statement'; #XXX configure... via event?? eh..
+my $statementnum = $3;
+
+my $conf = new FS::Conf;
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+my %payby = map { $_=>1 } @payby;
+
+my $cust_statement = qsearchs({
+ 'select' => 'cust_statement.*',
+ 'table' => 'cust_statement',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'statementnum' => $statementnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Statement #$statementnum not found!" unless $cust_statement;
+
+my $custnum = $cust_statement->custnum;
+my $display_custnum = $cust_statement->cust_main->display_custnum;
+
+my $link = "statementnum=$statementnum";
+$link .= ';template='. uri_escape($templatename) if $templatename;
+
+</%init>
diff --git a/httemplate/view/cust_svc.cgi b/httemplate/view/cust_svc.cgi
new file mode 100644
index 0000000..8ccfce3
--- /dev/null
+++ b/httemplate/view/cust_svc.cgi
@@ -0,0 +1,23 @@
+<% $cgi->redirect(popurl(1)."$svcdb.cgi?". $svcnum ) %>
+<%init>
+
+#needed here? we're just redirecting. i guess it could reveal the svcdb of a
+#svcnum... oooooo scary. not.
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+#some false laziness w/svc_*.cgi
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+die "Unknown svcnum" unless $cust_svc;
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $svcdb = $part_svc->svcdb;
+
+</%init>
+
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
new file mode 100644
index 0000000..852640e
--- /dev/null
+++ b/httemplate/view/elements/svc_Common.html
@@ -0,0 +1,167 @@
+<%doc>
+
+#Example:
+
+ include( 'elements/svc_Common.html,
+
+ 'table' => 'svc_something'
+
+ 'labels' => {
+ 'column' => 'Label',
+ },
+
+ #listref - each item is a literal column name (or method) or
+ # (notyet) coderef. if not specified all columns (except for the
+ #primary key) will be viewable
+ 'fields' => [
+ ]
+
+ # defaults to "edit/$table.cgi?", will have svcnum appended
+ 'edit_url' =>
+ )
+
+</%doc>
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View $label: $value") %>
+
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html","View $label: $value", menubar(
+ "Cancel this (unaudited) $label" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
+ )) %>
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this <% $label %>?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+% }
+
+Service #<B><% $svcnum %></B>
+% my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?';
+| <A HREF="<%$url%><%$svcnum%>">Edit this <% $label %></A>
+<BR>
+
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+
+% foreach my $f ( @$fields ) {
+%
+% my($field, $type);
+% if ( ref($f) ) {
+% $field = $f->{'field'},
+% $type = $f->{'type'} || 'text',
+% } else {
+% $field = $f;
+% $type = 'text';
+% }
+%
+% my $columndef = $part_svc->part_svc_column($field);
+% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
+
+ <TR>
+ <TD ALIGN="right">
+ <% ( $opt{labels} && exists $opt{labels}->{$field} )
+ ? $opt{labels}->{$field}
+ : $field
+ %>
+ </TD>
+
+% #eventually more options for <SELECT>, etc. fields
+
+ <TD BGCOLOR="#ffffff"><% $svc_x->$field %><TD>
+
+ </TR>
+
+% }
+%
+% }
+
+% foreach (sort { $a cmp $b } $svc_x->virtual_fields) {
+ <% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %>
+% }
+
+
+</TABLE></TD></TR></TABLE>
+
+<BR>
+
+% if ( defined($opt{'html_foot'}) ) {
+
+ <% ref($opt{'html_foot'})
+ ? &{ $opt{'html_foot'} }($svc_x)
+ : $opt{'html_foot'}
+ %>
+ <BR>
+
+% }
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my(%opt) = @_;
+
+my $table = $opt{'table'};
+
+my $fields = $opt{'fields'}
+ #|| [ grep { $_ ne 'svcnum' } dbdef->table($table)->columns ];
+ || [ grep { $_ ne 'svcnum' } fields($table) ];
+
+my $svcnum;
+if ( $cgi->param('svcnum') ) {
+ $cgi->param('svcnum') =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum = $1;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "no svcnum";
+ $svcnum = $1;
+}
+my $svc_x = qsearchs({
+ 'select' => $opt{'table'}.'.*',
+ 'table' => $opt{'table'},
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n";
+
+my $cust_svc = $svc_x->cust_svc;
+my($label, $value, $svcdb) = $cust_svc->label;
+
+my $part_svc = $cust_svc->part_svc;
+
+ #false laziness w/edit/svc_Common.html
+ #override default labels with service-definition labels if applicable
+ my $labels = $opt{labels}; #not -> here
+ foreach my $field ( keys %$labels ) {
+ my $col = $part_svc->part_svc_column($field);
+ $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\S*$/;
+ }
+
+my $pkgnum = $cust_svc->pkgnum;
+
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = $cust_svc->cust_pkg;
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+</%init>
diff --git a/httemplate/view/elements/svc_export_settings.html b/httemplate/view/elements/svc_export_settings.html
new file mode 100644
index 0000000..c5f2555
--- /dev/null
+++ b/httemplate/view/elements/svc_export_settings.html
@@ -0,0 +1,34 @@
+% if ( $FS::CurrentUser::CurrentUser->option('export_getsettings') ) {
+
+% my ( $settings, $defaults ) = $svc_x->export_getsettings;
+% if ( keys %$settings ) {
+
+%# a way to label this "Communigate pro settings".. just a config maybe... eh,
+%# its just for devel
+ External settings
+ <% ntable('#cccccc',2) %>
+
+% foreach my $key ( sort {$defaults->{$a} <=> $defaults->{$b} or $a cmp $b}
+% keys %$settings
+% )
+% {
+ <TR>
+ <TD ALIGN="right"><% $key |h %></TD>
+ <TD BGCOLOR="<% $defaults->{$key} ? '#eeeeee' : '#ffffff' %>">
+ <% $defaults->{$key} ? '<I>' : '<B>' %>
+ <% $settings->{$key} |h %>
+ <% $defaults->{$key} ? '</I>' : '</B>' %>
+ </TD>
+ </TR>
+% }
+
+ </TABLE>
+ <BR>
+
+% }
+% }
+<%init>
+
+my $svc_x = shift;
+
+</%init>
diff --git a/httemplate/view/elements/tr.html b/httemplate/view/elements/tr.html
new file mode 100644
index 0000000..e2ec7d4
--- /dev/null
+++ b/httemplate/view/elements/tr.html
@@ -0,0 +1,9 @@
+<TR>
+ <TD ALIGN="right"><% $opt{'label'} %></TD>
+ <TD BGCOLOR="#ffffff"><% $opt{'value'} %></TD>
+</TR>
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/view/logo.cgi b/httemplate/view/logo.cgi
new file mode 100644
index 0000000..aeca0f3
--- /dev/null
+++ b/httemplate/view/logo.cgi
@@ -0,0 +1,47 @@
+<% $data %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $type;
+if ( $cgi->param('type') eq 'png' ) {
+ $type = 'png';
+} elsif ( $cgi->param('type') eq 'eps' ) {
+ $type = 'eps';
+} else {
+ die "unknown logo type ". $cgi->param('type');
+}
+
+my $data;
+if ( $cgi->param('preview_session') =~ /^(\w+)$/ ) {
+
+ my $session = $1;
+ my $curuser = $FS::CurrentUser::CurrentUser;
+ $data = decode_base64( $curuser->option("logo_preview$session") );
+
+} elsif ( $cgi->param('name') =~ /^([^\.\/]*)$/ ) {
+
+ my $templatename = $1;
+ if ( $templatename && $conf->exists("logo_$templatename.$type") ) {
+ $templatename = "_$templatename";
+ } else {
+ $templatename = '';
+ }
+
+ if ( $type eq 'png' ) {
+ $data = $conf->config_binary("logo$templatename.png");
+ } elsif ( $type eq 'eps' ) {
+ #convert EPS to a png... punting on that for now
+ }
+
+} else {
+ die "neither a valid name nor a valid preview_session specified";
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>
+
diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html
new file mode 100644
index 0000000..9517e39
--- /dev/null
+++ b/httemplate/view/prospect_main.html
@@ -0,0 +1,93 @@
+<% include('/elements/header.html',
+ 'Prospect View: '. $prospect_main->company
+ )
+%>
+
+% if ( $curuser->access_right('Edit prospect') ) {
+ <A HREF="<% $p %>edit/prospect_main.html?<% $prospectnum %>">Edit this prospect</A>
+% }
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Prospect #</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $prospectnum %></B></TD>
+</TR>
+
+%unless ( scalar(@agentnums) == 1
+% && !$curuser->access_right('View customers of all agents') ) {
+% my $agent = qsearchs('agent',{ 'agentnum' => $prospect_main->agentnum } );
+ <TR>
+ <TD ALIGN="right">Agent</TD>
+ <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD>
+ </TR>
+%}
+
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD BGCOLOR="#FFFFFF"><B><% $prospect_main->company |h %></B></TD>
+</TR>
+
+% foreach my $contact ( $prospect_main->contact ) {
+ <TR>
+ <TD ALIGN="right">Contact</TD>
+ <TD BGCOLOR="#FFFFFF"><% $contact->line %></TD>
+ </TR>
+%}
+
+% my @cust_location =
+% qsearch('cust_location', { 'prospectnum' => $prospectnum } );
+% #but only one, for now
+% foreach my $cust_location (@cust_location) {
+ <TR>
+ <TD ALIGN="right">Address</TD>
+ <TD BGCOLOR="#FFFFFF">
+ <% $cust_location->location_label(
+ 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ )
+ %>
+ </TD>
+ </TR>
+% }
+
+</TABLE>
+
+<BR>
+
+<% ntable("#cccccc") %>
+
+<TR>
+ <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Tickets</FONT></TH>
+</TR>
+
+</TABLE>
+
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('View prospect');
+
+my $prospectnum;
+if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) {
+ $prospectnum = $1;
+} else {
+ die "No prospect specified (bad URL)!" unless $cgi->keywords;
+ my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+ $query =~ /^(\d+)$/;
+ $prospectnum = $1;
+}
+
+my $prospect_main = qsearchs( {
+ 'table' => 'prospect_main',
+ 'hashref' => { 'prospectnum' => $prospectnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Prospect not found!" unless $prospect_main;
+
+my @agentnums = $curuser->agentnums;
+
+</%init>
diff --git a/httemplate/view/svc_Common.html b/httemplate/view/svc_Common.html
new file mode 100644
index 0000000..defbee9
--- /dev/null
+++ b/httemplate/view/svc_Common.html
@@ -0,0 +1,29 @@
+<% include('elements/svc_Common.html',
+ 'table' => $table,
+ 'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=",
+ %opt,
+ )
+%>
+<%init>
+
+# false laziness w/edit/svc_Common.html
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+my %opt;
+if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
+ $opt{'name'} = "FS::$table"->table_info->{'name'};
+
+ my $fields = "FS::$table"->table_info->{'fields'};
+ my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+ $opt{'labels'} = \%labels;
+}
+
+</%init>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
new file mode 100755
index 0000000..9135e67
--- /dev/null
+++ b/httemplate/view/svc_acct.cgi
@@ -0,0 +1,141 @@
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View $svc account") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this account?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+ <% include("/elements/header.html",'View account', menubar(
+ "Cancel this (unaudited) account" =>
+ "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')",
+ )) %>
+
+% }
+
+<% include( 'svc_acct/radius_usage.html',
+ 'svc_acct' => $svc_acct,
+ 'part_svc' => $part_svc,
+ 'cust_pkg' => $cust_pkg,
+ %gopt,
+ )
+%>
+
+<% include( 'svc_acct/change_svc_form.html',
+ 'part_svc' => \@part_svc,
+ 'svcnum' => $svcnum,
+ 'pkgnum' => $pkgnum,
+ %gopt,
+ )
+%>
+
+Service #<B><% $svcnum %></B>
+| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A>
+
+<% include( 'svc_acct/change_svc.html',
+ 'part_svc' => \@part_svc,
+ %gopt,
+ )
+%>
+
+<% include( 'svc_acct/basics.html',
+ 'svc_acct' => $svc_acct,
+ 'part_svc' => $part_svc,
+ %gopt,
+ )
+%>
+
+</FORM>
+<BR>
+
+<% include( 'svc_acct/hosting.html',
+ %gopt,
+ )
+%>
+
+%#remove this? does anybody even use it? it was a misunderstood customer
+%#request IIRC?
+% my $conf = new FS::Conf;
+% if ( $conf->exists('svc_acct-notes') ) {
+% warn 'WARNING: svc_acct-notes deprecated\n';
+<% join("<BR>", $conf->config('svc_acct-notes') ) %>
+<BR><BR>
+% }
+
+<% include('elements/svc_export_settings.html', $svc_acct) %>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_acct = qsearchs({
+ 'select' => 'svc_acct.*',
+ 'table' => 'svc_acct',
+ 'addl_from' => $addl_from,
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+die "Unknown svcnum" unless $svc_acct;
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+my $svc = $part_svc->svc;
+
+my @part_svc = ();
+if ($FS::CurrentUser::CurrentUser->access_right('Change customer service')) {
+
+ if ( $pkgnum ) {
+ @part_svc = grep { $_->svcdb eq 'svc_acct'
+ && $_->svcpart != $part_svc->svcpart }
+ $cust_pkg->available_part_svc;
+ } else {
+ @part_svc = qsearch('part_svc', {
+ svcdb => 'svc_acct',
+ disabled => '',
+ svcpart => { op=>'!=', value=>$part_svc->svcpart },
+ } );
+ }
+
+}
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my %gopt = ( 'communigate' => $communigate,
+ );
+
+</%init>
diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html
new file mode 100644
index 0000000..f4c8388
--- /dev/null
+++ b/httemplate/view/svc_acct/basics.html
@@ -0,0 +1,136 @@
+<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<% include('/view/elements/tr.html', label=>'Service', value=>$part_svc->svc) %>
+<% include('/view/elements/tr.html', label=>'Username', value=>$svc_acct->username) %>
+<% include('/view/elements/tr.html', label=>'Domain', value=>$domain) %>
+
+% if ( $opt{'communigate'} ) {
+ <% include('/view/elements/tr.html', label=>'Aliases', value=>$svc_acct->cgp_aliases) %>
+%}
+
+% if ( $svc_acct->pbxsvc ) {
+ <% include('/view/elements/tr.html', label=>'PBX', value=>$svc_acct->pbx_title) %>
+%}
+
+% my $show_pw = '';
+% my $password = $svc_acct->get_cleartext_password;
+% if ( $password =~ /^\*\w+\* (.*)$/ ) {
+% $password = $1;
+% $show_pw .= '<I>(login disabled)</I> ';
+% }
+% if ( ! $password
+% && $svc_acct->_password_encryption ne 'plain'
+% && $svc_acct->_password
+% )
+% {
+% $show_pw .= '<I>('. uc($svc_acct->_password_encryption). ' encrypted)</I>';
+% } elsif ( $conf->exists('showpasswords') ) {
+% $show_pw .= '<PRE>'. encode_entities($password). '</PRE>';
+% } else {
+% $show_pw .= '<I>(hidden)</I>';
+% }
+% $password = '';
+<% include('/view/elements/tr.html', label=>'Password', value=>$show_pw) %>
+
+
+% if ( $conf->exists('security_phrase') ) {
+ <%include('/view/elements/tr.html', label=>'Security phrase', value=>$svc_acct->sec_phrase)%>
+% }
+
+% if ( $svc_acct->popnum ) {
+% my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum});
+ <% include('/view/elements/tr.html', label=>'Access number', value=>$svc_acct_pop->text) %>
+% }
+
+% if ($svc_acct->uid ne '') {
+ <% include('/view/elements/tr.html', label=>'UID', value=>$svc_acct->uid) %>
+% }
+
+% if ($svc_acct->gid ne '') {
+ <% include('/view/elements/tr.html', label=>'GID', value=>$svc_acct->gid) %>
+% }
+
+% if ($svc_acct->finger ne '') {
+ <% include('/view/elements/tr.html', label=>'Real Name', value=>$svc_acct->finger) %>
+% }
+
+% if ($svc_acct->dir ne '') {
+ <% include('/view/elements/tr.html', label=>'Home directory', value=>$svc_acct->dir) %>
+% }
+
+% if ($svc_acct->shell ne '') {
+ <% include('/view/elements/tr.html', label=>'Shell', value=>$svc_acct->shell) %>
+% }
+
+% if ($svc_acct->quota ne '' && ! $opt{'communigate'} ) {
+
+ <% include('/view/elements/tr.html', label=>'Quota', value=>$svc_acct->quota) %>
+
+% } elsif ( $opt{'communigate'} ) {
+
+ <% include( 'communigate.html', %opt ) %>
+
+% }
+
+% if ($svc_acct->slipip) {
+ <% include('/view/elements/tr.html',
+ label=>'IP address',
+ value=> ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' )
+ ? "<I>(Dynamic)</I>"
+ : $svc_acct->slipip
+ )
+ %>
+% }
+
+<% include('usage.html',
+ 'svc_acct' => $svc_acct,
+ )
+%>
+
+% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) {
+% $attribute =~ /^radius_(.*)$/;
+% my $pattribute = $FS::raddb::attrib{$1};
+ <% include('/view/elements/tr.html', label=>"Radius (reply) $pattribute",
+ value=>$svc_acct->getfield($attribute)
+ )
+ %>
+% }
+
+% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) {
+% $attribute =~ /^rc_(.*)$/;
+% my $pattribute = $FS::raddb::attrib{$1};
+ <% include('/view/elements/tr.html', label=>"Radius (check) $pattribute",
+ value=>$svc_acct->getfield($attribute)
+ )
+ %>
+% }
+
+<% include('/view/elements/tr.html', label=>'RADIUS groups',
+ value=>join('<BR>', $svc_acct->radius_groups) ) %>
+
+%# Can this be abstracted further? Maybe a library function like
+%# widget('HTML', 'view', $svc_acct) ? It would definitely make UI
+%# style management easier.
+% foreach (sort { $a cmp $b } $svc_acct->virtual_fields) {
+ <% $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %>
+% }
+
+</TABLE></TD></TR></TABLE>
+<%init>
+
+my %opt = @_;
+
+my $conf = new FS::Conf;
+
+my $svc_acct = $opt{'svc_acct'};
+my $part_svc = $opt{'part_svc'};
+
+die 'Empty domsvc for svc_acct.svcnum '. $svc_acct->svcnum
+ unless $svc_acct->domsvc;
+my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } );
+die 'Unknown domain (domsvc '. $svc_acct->domsvc.
+ ' for svc_acct.svcnum '. $svc_acct->svcnum. ')'
+ unless $svc_domain;
+my $domain = $svc_domain->domain;
+
+</%init>
diff --git a/httemplate/view/svc_acct/change_svc.html b/httemplate/view/svc_acct/change_svc.html
new file mode 100644
index 0000000..33d44a7
--- /dev/null
+++ b/httemplate/view/svc_acct/change_svc.html
@@ -0,0 +1,21 @@
+% if ( @part_svc || $opt{'showall'} ) {
+
+| <SELECT NAME="svcpart" onChange="enable_change()">
+ <OPTION VALUE="">Change service</OPTION>
+ <OPTION VALUE="">--------------</OPTION>
+% foreach my $opt_part_svc ( @part_svc ) {
+
+ <OPTION VALUE="<% $opt_part_svc->svcpart %>"><% $opt_part_svc->svc %></OPTION>
+% }
+
+ </SELECT>
+ <INPUT NAME="submit" TYPE="submit" VALUE="Change" disabled>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my @part_svc = @{ $opt{'part_svc'} };
+
+</%init>
diff --git a/httemplate/view/svc_acct/change_svc_form.html b/httemplate/view/svc_acct/change_svc_form.html
new file mode 100644
index 0000000..4f10922
--- /dev/null
+++ b/httemplate/view/svc_acct/change_svc_form.html
@@ -0,0 +1,23 @@
+% if ( @part_svc || $opt{'showall'} ) {
+ <SCRIPT TYPE="text/javascript">
+ function enable_change () {
+ if ( document.OneTrueForm.svcpart.selectedIndex > 1 ) {
+ document.OneTrueForm.submit.disabled = false;
+ } else {
+ document.OneTrueForm.submit.disabled = true;
+ }
+ }
+ </SCRIPT>
+
+ <FORM NAME="OneTrueForm" ACTION="<%$p%>edit/process/cust_svc.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+% }
+<%init>
+
+my %opt = @_;
+my @part_svc = @{ $opt{'part_svc'} };
+my $svcnum = $opt{'svcnum'};
+my $pkgnum = $opt{'pkgnum'};
+
+</%init>
diff --git a/httemplate/view/svc_acct/communigate.html b/httemplate/view/svc_acct/communigate.html
new file mode 100644
index 0000000..0f090fd
--- /dev/null
+++ b/httemplate/view/svc_acct/communigate.html
@@ -0,0 +1,115 @@
+%# settings
+
+ <% include('/view/elements/tr.html', label=>'Mailbox type', value=>$svc_acct->cgp_type) %>
+
+ <% include('/view/elements/tr.html', label=>'Enabled services',
+ value=>$svc_acct->cgp_accessmodes ) %>
+
+ <% include('/view/elements/tr.html', label=>'Mail storage limit',
+ value=>$svc_acct->quota ) %>
+
+ <% include('/view/elements/tr.html', label=>'File storage limit',
+ value=>$svc_acct->file_quota ) %>
+
+ <% include('/view/elements/tr.html', label=>'Number of files limit',
+ value=>$svc_acct->file_maxnum ) %>
+
+ <% include('/view/elements/tr.html', label=>'File size limit',
+ value=>$svc_acct->file_maxsize ) %>
+
+ <% include('/view/elements/tr.html', label=>'Password recovery',
+ value=>$svc_acct->password_recover ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Allowed mail rules',
+ value=>$svc_acct->cgp_rulesallowed || 'default (No)') %>
+
+ <% include('/view/elements/tr.html', label=>'RPOP modifications',
+ value=>$svc_acct->cgp_rpopallowed ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Accepts mail to "all"',
+ value=>$svc_acct->cgp_mailtoall ? 'YES' : 'NO' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Add trailer to sent mail',
+ value=>$svc_acct->cgp_addmailtrailer ? 'YES' : 'NO' ) %>
+
+%# preferences
+
+ <% include('/view/elements/tr.html', label=>'Message delete method',
+ value=>$svc_acct->cgp_deletemode ) %>
+
+ <% include('/view/elements/tr.html', label=>'On logout remove trash',
+ value=>$svc_acct->cgp_emptytrash ) %>
+
+ <% include('/view/elements/tr.html', label=>'Language',
+ value=>$svc_acct->cgp_language || 'default (English)' ) %>
+ <% include('/view/elements/tr.html', label=>'Time zone',
+ value=>$svc_acct->cgp_timezone || 'default (HostOS)' ) %>
+ <% include('/view/elements/tr.html', label=>'Layout',
+ value=>$svc_acct->cgp_skinname || 'default (***)' ) %>
+
+ <% include('/view/elements/tr.html', label=>'Pronto style',
+ value=>$svc_acct->cgp_prontoskinname ) %>
+
+ <% include('/view/elements/tr.html', label=>'Send read receipts',
+ value=>$svc_acct->cgp_sendmdnmode ) %>
+
+%# vacation message
+%#XXX finish me... do we need to search for specific rules
+%# (and hide them?) need to see what CGP gives back after we've added a rule
+ <% include('/elements/init_overlib.html') %>
+
+ <TR>
+ <TD ALIGN="right">Vacation message</TD>
+ <TD BGCOLOR="#FFFFFF">
+ <% include('/elements/popup_link.html',
+ 'action' => $p.'edit/cgp_rule-vacation.html?'.
+ 'svcnum='. $svc_acct->svcnum,
+ 'label' => '(add)', #XXX (edit)
+ 'actionlabel' => 'Vacation message',
+ 'width' => 600,
+ 'height' => 300,
+ #'color'
+ )
+ %>
+ </TD>
+ </TR>
+
+%# redirect all mail
+%#XXX finish me...
+
+ <TR>
+ <TD ALIGN="right">Redirect all mail</TD>
+ <TD BGCOLOR="#FFFFFF">
+ <% include('/elements/popup_link.html',
+ 'action' => $p.'edit/cgp_rule-redirect_all.html?'.
+ 'svcnum='. $svc_acct->svcnum,
+ 'label' => '(add)', #XXX (edit)
+ 'actionlabel' => 'Redirect all mail',
+ 'width' => 763,
+ #'height'
+ #'color'
+ )
+ %>
+ </TD>
+ </TR>
+
+%# mail rules
+
+ <% include('/view/elements/tr.html', label=>'Mail rules',
+ value=>$rule_link,
+ )
+ %>
+
+<%init>
+
+my %opt = @_;
+
+#my $conf = new FS::Conf;
+
+my $svc_acct = $opt{'svc_acct'};
+#my $part_svc = $opt{'part_svc'};
+
+my $rule_link = qq(<A HREF="${p}browse/cgp_rule.html?svcnum=).
+ $svc_acct->svcnum. '">View/edit mail rules</A>';
+
+</%init>
diff --git a/httemplate/view/svc_acct/hosting.html b/httemplate/view/svc_acct/hosting.html
new file mode 100644
index 0000000..1d83603
--- /dev/null
+++ b/httemplate/view/svc_acct/hosting.html
@@ -0,0 +1,38 @@
+% if ( @svc_www || $opt{'showall'} ) {
+ Hosting
+ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+% foreach my $svc_www (@svc_www) {
+% my($label, $value) = $svc_www->cust_svc->label;
+% my $link = $p. 'view/svc_www.cgi?'. $svc_www->svcnum;
+ <TR>
+ <TD BGCOLOR="#ffffff">
+ <A HREF="<% $link %>"><% "$label: $value" %></A>
+ </TD>
+ </TR>
+% }
+ </TABLE></TD></TR></TABLE>
+ <BR><BR>
+% }
+<%init>
+
+my %opt = @_;
+
+#false laziness w/view_svc_acct.cgi and a zillion other places
+my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ';
+
+my @svc_www = qsearch({
+ 'select' => 'svc_www.*',
+ 'table' => 'svc_www',
+ 'addl_from' => $addl_from,
+ 'hashref' => { 'usersvc' => $opt{'svcnum'} },
+ #XXX shit outta luck if you somehow got them linked across agents
+ # maybe we should show but not link to them? kinda makes sense...
+ # (maybe a specific ACL for this situation???)
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+
+</%init>
diff --git a/httemplate/view/svc_acct/radius_usage.html b/httemplate/view/svc_acct/radius_usage.html
new file mode 100644
index 0000000..e2253a3
--- /dev/null
+++ b/httemplate/view/svc_acct/radius_usage.html
@@ -0,0 +1,77 @@
+% if ( $part_svc->part_export_usage ) {
+%
+% my $last_bill;
+% my %plandata;
+% if ( $cust_pkg ) {
+% #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really
+% #belong in plan data
+% %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+% split("\n", $cust_pkg->part_pkg->plandata );
+%
+% $last_bill = $cust_pkg->last_bill;
+% } else {
+% $last_bill = 0;
+% %plandata = ();
+% }
+%
+% my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time );
+% my $hour = int($seconds/3600);
+% my $min = int( ($seconds%3600) / 60 );
+% my $sec = $seconds%60;
+%
+% my $input = $svc_acct->attribute_since_sqlradacct(
+% $last_bill, time, 'AcctInputOctets'
+% ) / 1048576;
+% my $output = $svc_acct->attribute_since_sqlradacct(
+% $last_bill, time, 'AcctOutputOctets'
+% ) / 1048576;
+%
+%
+
+
+ RADIUS session information<BR>
+ <% ntable('#cccccc',2) %>
+ <TR><TD BGCOLOR="#ffffff">
+% if ( $seconds ) {
+
+ Online <B><% $hour %></B>h <B><% $min %></B>m <B><% $sec %></B>s
+% } else {
+
+ Has not logged on
+% }
+% if ( $cust_pkg ) {
+
+ since last bill (<% time2str('%a %b %o %Y', $last_bill) %>)
+% if ( length($plandata{recur_included_hours}) ) {
+
+ - <% $plandata{recur_included_hours} %> total hours in plan
+% }
+
+ <BR>
+% } else {
+
+ (no billing cycle available for unaudited account)<BR>
+% }
+
+
+ Upload: <B><% sprintf("%.3f", $input) %></B> megabytes<BR>
+ Download: <B><% sprintf("%.3f", $output) %></B> megabytes<BR>
+ Last Login: <B><% $svc_acct->last_login_text %></B><BR>
+% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=!. $svc_acct->svcnum;
+
+ View session detail:
+ <% $href %>;begin=<% $last_bill %>">this billing cycle</A>
+ | <% $href %>;begin=<% time-15552000 %>">past six months</A>
+ | <% $href %>">all sessions</A>
+
+ </TD></TR></TABLE><BR>
+% }
+<%init>
+
+my %opt = @_;
+
+my $svc_acct = $opt{'svc_acct'};
+my $part_svc = $opt{'part_svc'};
+my $cust_pkg = $opt{'cust_pkg'};
+
+</%init>
diff --git a/httemplate/view/svc_acct/usage.html b/httemplate/view/svc_acct/usage.html
new file mode 100644
index 0000000..9758d83
--- /dev/null
+++ b/httemplate/view/svc_acct/usage.html
@@ -0,0 +1,27 @@
+% my %ulabel = ( seconds => 'Time',
+% upbytes => 'Upload bytes',
+% downbytes => 'Download bytes',
+% totalbytes => 'Total bytes',
+% );
+% foreach my $uf ( keys %ulabel ) {
+% my $tf = $uf . "_threshold";
+% if ( $svc_acct->$uf ne '' ) {
+% my $v = $uf eq 'seconds'
+% #? (($svc_acct->$uf < 0 ? '-' : ''). duration_exact($svc_acct->$uf) )
+% ? ($svc_acct->$uf < 0 ? '-' : '').
+% int(abs($svc_acct->$uf)/3600). "hr ".
+% sprintf("%02d",(abs($svc_acct->$uf)%3600)/60). "min"
+% : FS::UI::bytecount::display_bytecount($svc_acct->$uf);
+ <TR>
+ <TD ALIGN="right"><% $ulabel{$uf} %> remaining</TD>
+ <TD BGCOLOR="#ffffff"><% $v %></TD>
+ </TR>
+
+% }
+% }
+<%init>
+
+my %opt = @_;
+my $svc_acct = $opt{'svc_acct'};
+
+</%init>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644
index 0000000..f552e9b
--- /dev/null
+++ b/httemplate/view/svc_broadband.cgi
@@ -0,0 +1,219 @@
+<%include("/elements/header.html",'Broadband Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ )
+))
+%>
+
+<% include('/elements/init_overlib.html') %>
+
+<A HREF="<%$p%>edit/svc_broadband.cgi?<%$svcnum%>">Edit this information</A>
+<BR>
+<%ntable("#cccccc")%>
+ <TR>
+ <TD>
+ <%ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%$svcnum%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Description</TD>
+ <TD BGCOLOR="#ffffff"><%$description%></TD>
+ </TR>
+
+% if ( $router ) {
+ <TR>
+ <TD ALIGN="right">Router</TD>
+ <TD BGCOLOR="#ffffff"><%$router->routernum%>: <%$router->routername%></TD>
+ </TR>
+% }
+
+ <TR>
+ <TD ALIGN="right">Download Speed</TD>
+ <TD BGCOLOR="#ffffff"><%$speed_down%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Upload Speed</TD>
+ <TD BGCOLOR="#ffffff"><%$speed_up%></TD>
+ </TR>
+
+% if ( $ip_addr ) {
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff">
+ <%$ip_addr%>
+ (<% include('/elements/popup_link-ping.html', 'ip'=>$ip_addr ) %>)
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Netmask</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->NetAddr->mask%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Gateway</TD>
+ <TD BGCOLOR="#ffffff"><%$addr_block->ip_gateway%></TD>
+ </TR>
+% }
+
+ <TR>
+ <TD ALIGN="right">MAC Address</TD>
+ <TD BGCOLOR="#ffffff"><%$mac_addr%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Latitude</TD>
+ <TD BGCOLOR="#ffffff"><%$latitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Longitude</TD>
+ <TD BGCOLOR="#ffffff"><%$longitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Altitude</TD>
+ <TD BGCOLOR="#ffffff"><%$altitude%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">VLAN Profile</TD>
+ <TD BGCOLOR="#ffffff"><%$vlan_profile%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Authentication Key</TD>
+ <TD BGCOLOR="#ffffff"><%$auth_key%></TD>
+ </TR>
+ <TR COLSPAN="2"><TD></TD></TR>
+%
+%foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) {
+% print $svc_broadband->pvf($_)->widget('HTML', 'view',
+% $svc_broadband->getfield($_)), "\n";
+%}
+%
+%
+
+ </TABLE>
+ </TD>
+ </TR>
+</TABLE>
+
+<BR>
+<%ntable("#cccccc", 2)%>
+%
+% my $sb_router = qsearchs('router', { svcnum => $svcnum });
+% if ($sb_router) {
+%
+
+ <B>Router associated: <%$sb_router->routername%> </B>
+ <A HREF="<%popurl(2)%>edit/router.cgi?<%$sb_router->routernum%>">
+ (details)
+ </A>
+ <BR>
+% my @sb_addr_block;
+% if (@sb_addr_block = $sb_router->addr_block) {
+%
+
+ <B>Address space </B>
+ <A HREF="<%popurl(2)%>browse/addr_block.cgi">
+ (edit)
+ </A>
+ <BR>
+% print ntable("#cccccc", 1);
+% foreach (@sb_addr_block) {
+
+ <TR>
+ <TD><%$_->ip_gateway%>/<%$_->ip_netmask%></TD>
+ </TR>
+% }
+
+ </TABLE>
+% } else {
+
+ <B>No address space allocated.</B>
+% }
+
+ <BR>
+%
+% } else {
+%
+
+
+<FORM METHOD="GET" ACTION="<%popurl(2)%>edit/router.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+Add router named
+ <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%$svcnum%>)">
+ <INPUT TYPE="submit" VALUE="Add router">
+</FORM>
+%
+%}
+%
+
+
+<BR>
+<%joblisting({'svcnum'=>$svcnum}, 1)%>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_broadband = qsearchs({
+ 'select' => 'svc_broadband.*',
+ 'table' => 'svc_broadband',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) or die "svc_broadband: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $addr_block = $svc_broadband->addr_block;
+my $router = $addr_block->router if $addr_block;
+
+#if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+
+my (
+ $speed_down,
+ $speed_up,
+ $ip_addr,
+ $mac_addr,
+ $latitude,
+ $longitude,
+ $altitude,
+ $vlan_profile,
+ $auth_key,
+ $description,
+ ) = (
+ $svc_broadband->getfield('speed_down'),
+ $svc_broadband->getfield('speed_up'),
+ $svc_broadband->getfield('ip_addr'),
+ $svc_broadband->mac_addr,
+ $svc_broadband->latitude,
+ $svc_broadband->longitude,
+ $svc_broadband->altitude,
+ $svc_broadband->vlan_profile,
+ $svc_broadband->auth_key,
+ $svc_broadband->description,
+ );
+
+</%init>
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
new file mode 100755
index 0000000..3938a34
--- /dev/null
+++ b/httemplate/view/svc_domain.cgi
@@ -0,0 +1,82 @@
+% if ( $custnum ) {
+
+%# <% include("/elements/header.html","View $svcdomain") %>
+ <% include("/elements/header.html","View domain") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html",'View domain', menubar(
+ "Cancel this (unaudited) domain" =>
+ "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum', 'Delete $domain and all records?')",
+ ))
+ %>
+
+% }
+
+<% include('/elements/error.html') %>
+
+<% include('svc_domain/basics.html', $svc_domain,
+ 'part_svc' => $part_svc,
+ 'custnum' => $custnum,
+ )
+%>
+<BR>
+
+<% include('svc_domain/acct_defaults.html', $svc_domain,
+ 'part_svc' => $part_svc,
+ )
+%>
+<BR>
+
+<% include('svc_domain/dns.html', $svc_domain ) %>
+<BR>
+
+<% include('elements/svc_export_settings.html', $svc_domain) %>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_domain = qsearchs({
+ 'select' => 'svc_domain.*',
+ 'table' => 'svc_domain',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+die "Unknown svcnum" unless $svc_domain;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs('cust_pkg', {'pkgnum'=>$pkgnum} );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $domain = $svc_domain->domain;
+
+</%init>
diff --git a/httemplate/view/svc_domain/acct_defaults.html b/httemplate/view/svc_domain/acct_defaults.html
new file mode 100644
index 0000000..3a4e187
--- /dev/null
+++ b/httemplate/view/svc_domain/acct_defaults.html
@@ -0,0 +1,141 @@
+% if ( $communigate ) {
+
+ Account defaults
+ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+%# settings
+
+ <% include('/view/elements/tr.html',
+ label=>'Password modification',
+ value=>$svc_domain->acct_def_password_selfchange ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Password recovery',
+ value=>$svc_domain->acct_def_password_recover ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Enabled services',
+ value=>$svc_domain->acct_def_cgp_accessmodes,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Mail storage limit',
+ value=>$svc_domain->acct_def_quota,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'File storage limit',
+ value=>$svc_domain->acct_def_file_quota,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Files limit',
+ value=>$svc_domain->acct_def_file_maxnum,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'File size limit',
+ value=>$svc_domain->acct_def_file_maxsize,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Allowed mail rules',
+ value=>$svc_domain->acct_def_cgp_rulesallowed || 'default (No)',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'RPOP modifications',
+ value=>$svc_domain->acct_def_cgp_rpopallowed ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Accepts mail to "all"',
+ value=>$svc_domain->acct_def_cgp_mailtoall ? 'YES' : 'NO',
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Add trailer to sent mail',
+ value=>$svc_domain->acct_def_cgp_addmailtrailer ? 'YES' : 'NO',
+ )
+ %>
+
+%# preferences
+
+ <% include('/view/elements/tr.html',
+ label=>'Message delete method',
+ value=>$svc_domain->acct_def_cgp_deletemode,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'On logout remove trash',
+ value=>$svc_domain->acct_def_cgp_emptytrash,
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Language',
+ value=>$svc_domain->acct_def_cgp_language || 'default (English)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Time zone',
+ value=>$svc_domain->acct_def_cgp_timezone || 'default (HostOS)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Layout',
+ value=>$svc_domain->acct_def_cgp_skinname || 'default (***)'
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Pronto style',
+ value=>$svc_domain->acct_def_cgp_prontoskinname
+ )
+ %>
+
+ <% include('/view/elements/tr.html',
+ label=>'Send read receipts',
+ value=>$svc_domain->acct_def_cgp_sendmdnmode
+ )
+ %>
+
+%# mail
+%#XXX rules, archive rule, spam foldering rule(s)
+
+ </TABLE></TD></TR></TABLE>
+
+% }
+<%init>
+
+my($svc_domain, %opt) = @_;
+
+my $part_svc = $opt{'part_svc'};
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my %rulesallowed = (
+ -1 => 'default (No)', #No always the default?
+ 0 => 'No',
+ 1 => 'Filter Only',
+ 2 => 'All But Exec',
+ 3 => 'Any',
+);
+
+</%init>
diff --git a/httemplate/view/svc_domain/basics.html b/httemplate/view/svc_domain/basics.html
new file mode 100644
index 0000000..1915719
--- /dev/null
+++ b/httemplate/view/svc_domain/basics.html
@@ -0,0 +1,151 @@
+Service #<B><% $svcnum %></B>
+% #if ( $conf->exists('svc_domain-edit_domain') ) {
+ | <A HREF="<%$p%>edit/svc_domain.cgi?<%$svcnum%>">Edit this domain</A>
+% #}
+
+<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#ffffff"><% $part_svc->svc %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD BGCOLOR="#ffffff">
+ <B><% $domain %></B>
+ <A HREF="<% ${p} %>misc/whois.cgi?custnum=<%$custnum%>;svcnum=<%$svcnum%>;domain=<%$domain%>">(view whois information)</A>
+ </TD>
+</TR>
+
+% if ($export) {
+ <TR>
+ <TD ALIGN="right">Registration status</TD>
+ <TD BGCOLOR="#ffffff"><B><% $status %></B>
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Manage domain registration') ) {
+% if ( defined($ops{'register'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=register&svcnum=<% $svcnum %>">Register at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'transfer'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=transfer&svcnum=<% $svcnum %>">Transfer to <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'renew'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=renew&svcnum=<% $svcnum %>&period=1">Renew at <% $registrar->{'name'} %></A>&nbsp;
+% }
+% if ( defined($ops{'revoke'}) ) {
+ <A HREF="<% ${p} %>edit/process/domreg.cgi?op=revoke&svcnum=<% $svcnum %>">Revoke</A>
+% }
+% }
+
+ </TD>
+ </TR>
+% }
+
+% if ( $communigate ) {
+
+ <TR>
+ <TD ALIGN="right">Administrator domain</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $svc_domain->parent_svcnum ) {
+% #XXX agent-virt aware the link
+ <A HREF="svc_domain.cgi?<% $svc_domain->parent_svcnum %>"><% $svc_domain->parent_svc_x->domain %></A>
+% } else {
+ <I>(none)</I>
+% }
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Aliases</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->cgp_aliases %></TD>
+ </TR>
+
+% }
+
+% if ( $communigate && $svc_domain->max_accounts ) {
+ <TR>
+ <TD ALIGN="right">Maximum number of Accounts</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->max_accounts %></TD>
+ </TR>
+% }
+
+<TR>
+ <TD ALIGN="right">Catch all email</TD>
+ <TD BGCOLOR="#ffffff"><% $email ? "<B>$email</B>" : '<I>(none)</I>' %>
+% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall') ) {
+ <A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>
+% }
+ </TD>
+</TR>
+
+% if ( $svc_domain->cgp_accessmodes ) {
+ <TR>
+ <TD ALIGN="right">Enabled services</TD>
+ <TD BGCOLOR="#ffffff"><% $svc_domain->cgp_accessmodes %></TD>
+ </TR>
+% }
+
+% if ( $svc_domain->trailer ) {
+ <TR>
+ <TD ALIGN="right">Mail trailer</TD>
+ <TD BGCOLOR="#ffffff"><PRE><% $svc_domain->trailer |h %></PRE></TD>
+ </TR>
+% }
+
+% if ( $communigate ) {
+% my $rule_url = $p. 'browse/cgp_rule.html?svcnum='. $svc_domain->svcnum;
+ <TR>
+ <TD ALIGN="right">Doimain mail rules</TD>
+ <TD BGCOLOR="#ffffff"><A HREF="<% $rule_url %>">View/Edit domain mail rules</A></TD>
+ </TR>
+% }
+
+</TABLE></TD></TR></TABLE>
+
+<%init>
+
+my($svc_domain, %opt) = @_;
+my $svcnum = $svc_domain->svcnum;
+my $domain = $svc_domain->domain;
+my $custnum = $opt{'custnum'};
+my $part_svc = $opt{'part_svc'};
+
+my $communigate = scalar($part_svc->part_export('communigate_pro'));
+ # || scalar($part_svc->part_export('communigate_pro_singledomain'));
+
+my $email = '';
+if ($svc_domain->catchall) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } );
+ die "Unknown svcpart" unless $svc_acct;
+ $email = $svc_acct->email;
+}
+
+# Find the first export that does domain registration
+my @exports = grep $_->can('registrar'), $part_svc->part_export;
+my $export = $exports[0];
+# If we have a domain registration export, get the registrar object
+my $registrar;
+my $status = 'Unknown';
+my %ops = ();
+if ($export) {
+ $registrar = $export->registrar;
+ my $domstat = $export->get_status( $svc_domain );
+ if (defined($domstat->{'message'})) {
+ $status = $domstat->{'message'};
+ } elsif (defined($domstat->{'unregistered'})) {
+ $status = 'Not registered';
+ $ops{'register'} = "Register";
+ } elsif (defined($domstat->{'status'})) {
+ $status = $domstat->{'status'} . ' ' . $domstat->{'contact_email'} . ' ' . $domstat->{'last_update_time'};
+ } elsif (defined($domstat->{'expdate'})) {
+ $status = "Expires " . $domstat->{'expdate'};
+ $ops{'renew'} = "Renew";
+ $ops{'revoke'} = "Revoke";
+ } else {
+ $status = $domstat->{'reason'};
+ $ops{'transfer'} = "Transfer";
+ }
+}
+
+</%init>
diff --git a/httemplate/view/svc_domain/dns.html b/httemplate/view/svc_domain/dns.html
new file mode 100644
index 0000000..88a9bda
--- /dev/null
+++ b/httemplate/view/svc_domain/dns.html
@@ -0,0 +1,95 @@
+<SCRIPT>
+ function areyousure(href, message) {
+ if ( confirm(message) == true )
+ window.location.href = href;
+ }
+ function slave_areyousure() {
+ return confirm("Remove all records and slave from " + document.SlaveForm.recdata.value + "?");
+ }
+</SCRIPT>
+
+DNS records
+% my @records; if ( @records = $svc_domain->domain_record ) {
+
+ <% include('/elements/table-grid.html') %>
+
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = $bgcolor2;
+
+ <tr>
+ <th CLASS="grid" BGCOLOR="#cccccc">Zone</th>
+ <th CLASS="grid" BGCOLOR="#cccccc">Type</th>
+ <th CLASS="grid" BGCOLOR="#cccccc">Data</th>
+ </tr>
+
+% foreach my $domain_record ( @records ) {
+% my $type = $domain_record->rectype eq '_mstr'
+% ? "(slave)"
+% : $domain_record->recaf. ' '. $domain_record->rectype;
+
+
+ <tr>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->reczone %></td>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $type %></td>
+ <td CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $domain_record->recdata %>
+
+% unless ( $domain_record->rectype eq 'SOA'
+% || ! $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice')
+% ) {
+% ( my $recdata = $domain_record->recdata ) =~ s/"/\\'\\'/g;
+ (<A HREF="javascript:areyousure('<%$p%>misc/delete-domain_record.cgi?<%$domain_record->recnum%>', 'Delete \'<% $domain_record->reczone %> <% $type %> <% $recdata %>\' ?' )">delete</A>)
+% }
+ </td>
+ </tr>
+
+
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+% }
+
+ </table>
+% }
+
+% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice') ) {
+ <FORM METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+ <INPUT TYPE="text" NAME="reczone">
+ <INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN
+ <SELECT NAME="rectype">
+% foreach (qw( A NS CNAME MX PTR TXT) ) {
+ <OPTION VALUE="<%$_%>"><%$_%></OPTION>
+% }
+ </SELECT>
+ <INPUT TYPE="text" NAME="recdata">
+ <INPUT TYPE="submit" VALUE="Add record">
+ </FORM>
+
+ <FORM NAME="SlaveForm" METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+ Or
+% if ( @records ) {
+ delete all records and
+% }
+ slave from nameserver IP
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+ <INPUT TYPE="hidden" NAME="reczone" VALUE="@">
+ <INPUT TYPE="hidden" NAME="recaf" VALUE="IN">
+ <INPUT TYPE="hidden" NAME="rectype" VALUE="_mstr">
+ <INPUT TYPE="text" NAME="recdata">
+ <INPUT TYPE="submit" VALUE="Slave domain" onClick="return slave_areyousure()">
+ </FORM>
+
+% }
+
+<%init>
+
+my($svc_domain, %opt) = @_;
+my $svcnum = $svc_domain->svcnum;
+
+</%init>
+
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
new file mode 100644
index 0000000..77679d8
--- /dev/null
+++ b/httemplate/view/svc_external.cgi
@@ -0,0 +1,65 @@
+<% include("/elements/header.html",'External Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) external service" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+)) %>
+
+<A HREF="<%$p%>edit/svc_external.cgi?<%$svcnum%>">Edit this information</A><BR>
+<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
+
+<TR><TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><% $svcnum %></TD></TR>
+<TR><TD ALIGN="right"><% FS::Msgcat::_gettext('svc_external-id') || 'External&nbsp;ID' %></TD>
+ <TD BGCOLOR="#ffffff"><% $conf->config('svc_external-display_type') eq 'artera_turbo' ? sprintf('%010d', $svc_external->id) : $svc_external->id %></TD></TR>
+<TR><TD ALIGN="right"><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TD>
+ <TD BGCOLOR="#ffffff"><% $svc_external->title %></TD></TR>
+% foreach (sort { $a cmp $b } $svc_external->virtual_fields) {
+
+ <% $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %>
+% }
+
+
+</TABLE></TD></TR></TABLE>
+<BR><% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_external = qsearchs({
+ 'select' => 'svc_external.*',
+ 'table' => 'svc_external',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) or die "svc_external: Unknown svcnum $svcnum";
+
+my $conf = new FS::Conf;
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+</%init>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
new file mode 100755
index 0000000..15b5ae5
--- /dev/null
+++ b/httemplate/view/svc_forward.cgi
@@ -0,0 +1,124 @@
+% if ( $custnum ) {
+
+ <% include("/elements/header.html","View mail forward") %>
+ <% include( '/elements/small_custview.html', $custnum, '', 1,
+ "${p}view/cust_main.cgi") %>
+ <BR>
+
+% } else {
+
+ <% include("/elements/header.html",'View mail forward', menubar(
+ "Cancel this (unaudited) mail forward" =>
+ "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum')",
+ ))
+ %>
+
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Permanently delete this mail forward?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+
+% }
+
+<A HREF="<% $p %>edit/svc_forward.cgi?<% $svcnum %>">Edit this information</A>
+
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><% $svcnum %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#ffffff"><% $svc %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Email to</TD>
+ <TD BGCOLOR="#ffffff"><% $source %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Forwards to </TD>
+ <TD BGCOLOR="#ffffff"><% $destination %></TD>
+ </TR>
+
+% foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+ <% $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)) %>
+% }
+
+</TABLE>
+
+<BR>
+
+<% include('elements/svc_export_settings.html', $svc_forward) %>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_forward = qsearchs({
+ 'select' => 'svc_forward.*',
+ 'table' => 'svc_forward',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => {'svcnum'=>$svcnum},
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+});
+die "Unknown svcnum" unless $svc_forward;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ $custnum=$cust_pkg->getfield('custnum');
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } )
+ or die "Unknown svcpart";
+
+my($srcsvc,$dstsvc,$dst) = (
+ $svc_forward->srcsvc,
+ $svc_forward->dstsvc,
+ $svc_forward->dst,
+);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+
+my $svc = $part_svc->svc;
+
+my $source;
+if ($srcsvc) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc})
+ or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc";
+ $source = $svc_acct->email;
+} else {
+ $source = $src;
+}
+
+my $destination;
+if ($dstsvc) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc})
+ or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc";
+ $destination = $svc_acct->email;
+} else {
+ $destination = $dst;
+}
+
+</%init>
diff --git a/httemplate/view/svc_mailinglist.cgi b/httemplate/view/svc_mailinglist.cgi
new file mode 100644
index 0000000..f646a41
--- /dev/null
+++ b/httemplate/view/svc_mailinglist.cgi
@@ -0,0 +1,71 @@
+<% include('elements/svc_Common.html',
+ 'table' => 'svc_mailinglist',
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = ();
+
+my $info = FS::svc_mailinglist->table_info;
+
+$opt{'name'} = $info->{'name'};
+
+my $fields = $info->{'fields'};
+my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+
+#$opt{'fields'} = [ keys %$fields ];
+$opt{'fields'} = [
+ 'username',
+ 'domain',
+ 'listname',
+ 'reply_to',
+ 'remove_from',
+ 'reject_auto',
+ 'remove_to_and_cc',
+];
+
+$opt{'labels'} = \%labels;
+
+$opt{'html_foot'} = sub {
+ my $svc_mailinglist = shift;
+ my $listnum = $svc_mailinglist->listnum;
+
+ my $sql = 'SELECT COUNT(*) FROM mailinglistmember WHERE listnum = ?';
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute($listnum) or die $sth->errstr;
+ my $num = $sth->fetchrow_arrayref->[0];
+
+ my $add_url = $p."edit/mailinglistmember.html?listnum=$listnum";
+
+ my $add_link = include('/elements/init_overlib.html').
+ include('/elements/popup_link.html',
+ 'action' => $add_url,
+ 'label' => 'add',
+ 'actionlabel' => 'Add list member',
+ 'width' => 392,
+ 'height' => 192,
+ );
+
+ ntable('#cccccc').'<TR><TD>'.ntable('#cccccc',2). qq[
+ <TR>
+ <TD>List members</TD>
+ <TD BGCOLOR="#ffffff">
+ $num members
+ ( <A HREF="${p}search/mailinglistmember.html?listnum=$listnum">view</A>
+ | $add_link )
+ </TD>
+ </TR>
+ </TABLE></TD></TR></TABLE>
+
+ <BR><BR>
+ ]. include('svc_export_settings.html', $svc_mailinglist);
+
+};
+
+</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
new file mode 100644
index 0000000..75591c7
--- /dev/null
+++ b/httemplate/view/svc_phone.cgi
@@ -0,0 +1,153 @@
+<% include('elements/svc_Common.html',
+ 'table' => 'svc_phone',
+ 'fields' => \@fields,
+ 'labels' => {
+ 'countrycode' => 'Country code',
+ 'phonenum' => 'Phone number',
+ 'domain' => 'Domain',
+ 'pbx_title' => 'PBX',
+ 'sip_password' => 'SIP password',
+ 'pin' => 'PIN',
+ 'phone_name' => 'Name',
+ },
+ 'html_foot' => $html_foot,
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+
+my @fields = qw( countrycode phonenum );
+push @fields, 'domain' if $conf->exists('svc_phone-domain');
+push @fields, qw( pbx_title sip_password pin phone_name );
+
+my $html_foot = sub {
+ my $svc_phone = shift;
+
+ ###
+ # E911 Info
+ ###
+
+ my $e911 =
+ 'E911 Information'.
+ &ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2).
+ '<TR><TD>Location</TD>'.
+ '<TD BGCOLOR="#FFFFFF">'.
+ $svc_phone->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' &nbsp; ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ ).
+ '</TD></TR>'.
+ '</TABLE></TD></TR></TABLE>'.
+ '<BR>'
+ ;
+
+ ###
+ # Devices
+ ###
+
+ my $devices = '';
+
+ my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device") #WHERE disabled = '' OR disabled IS NULL;");
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ my $num_part_device = $sth->fetchrow_arrayref->[0];
+
+ my @phone_device = $svc_phone->phone_device;
+ if ( @phone_device || $num_part_device ) {
+ my $svcnum = $svc_phone->svcnum;
+ $devices .=
+ qq[Devices (<A HREF="${p}edit/phone_device.html?svcnum=$svcnum">Add device</A>)<BR>];
+ if ( @phone_device ) {
+
+ $devices .= qq!
+ <SCRIPT>
+ function areyousure(href) {
+ if (confirm("Are you sure you want to delete this device?") == true)
+ window.location.href = href;
+ }
+ </SCRIPT>
+ !;
+
+
+ $devices .=
+ include('/elements/table-grid.html').
+ '<TR>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+ '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'.
+ '</TR>';
+ my $bgcolor1 = '#eeeeee';
+ my $bgcolor2 = '#ffffff';
+ my $bgcolor = '';
+
+ foreach my $phone_device ( @phone_device ) {
+
+ if ( $bgcolor eq $bgcolor1 ) {
+ $bgcolor = $bgcolor2;
+ } else {
+ $bgcolor = $bgcolor1;
+ }
+ my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">);
+
+ my $devicenum = $phone_device->devicenum;
+ my $export_links = join( '<BR>', @{ $phone_device->export_links } );
+
+ $devices .= '<TR>'.
+ $td. $phone_device->part_device->devicename. '</TD>'.
+ $td. $phone_device->mac_addr. '</TD>'.
+ $td. $export_links. '</TD>'.
+ "$td( ".
+ qq(<A HREF="${p}edit/phone_device.html?$devicenum">edit</A> | ).
+ qq(<A HREF="javascript:areyousure('${p}misc/delete-phone_device.html?$devicenum')">delete</A>).
+ ' )</TD>'.
+ '</TR>';
+ }
+ $devices .= '</TABLE><BR>';
+ }
+ $devices .= '<BR>';
+ }
+
+ ##
+ # CDR links
+ ##
+
+ tie my %what, 'Tie::IxHash',
+ 'pending' => 'NULL',
+ 'billed' => 'done',
+ ;
+
+ #XXX src & charged party (& default prefix) as per voip_cdr.pm
+ #XXX handle toll free too
+
+ my $number = $svc_phone->phonenum;
+ $number = $svc_phone->countrycode. $number
+ unless $svc_phone->countrycode eq '1';
+
+ #my @links = map {
+ # qq(<A HREF="${p}search/cdr.html?src=$number;freesidestatus=$what{$_}">).
+ # "View $_ CDRs</A>";
+ #} keys(%what);
+ my @links = map {
+ qq(<A HREF="${p}search/cdr.html?cdrbatchnum=__ALL__;charged_party=$number;freesidestatus=$what{$_}">).
+ "View $_ CDRs</A>";
+ } keys(%what);
+
+ my @ilinks = ( qq(<A HREF="${p}search/cdr.html?cdrbatchnum=__ALL__;dst=$number">).
+ 'View incoming CDRs</A>' );
+
+ ###
+ # concatenate & return
+ ###
+
+ $e911.
+ $devices.
+ join(' | ', @links ). '<BR>'.
+ join(' | ', @ilinks). '<BR>';
+
+};
+
+</%init>
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
new file mode 100644
index 0000000..935d139
--- /dev/null
+++ b/httemplate/view/svc_www.cgi
@@ -0,0 +1,106 @@
+<% include("/elements/header.html", "Website View", menubar(
+ ( ( $custnum )
+ ? ( "View this customer (#$display_custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ ))
+%>
+
+<A HREF="<% $p %>edit/svc_www.cgi?<% $svcnum %>">Edit this information</A><BR>
+
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><% $svcnum %></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Website name</TD>
+ <TD BGCOLOR="#ffffff"><A HREF="http://<% $www %>"><% $www %></A></TD>
+ </TR>
+
+% if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F'
+% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) {
+
+ <TR>
+ <TD ALIGN="right">Account</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $usersvc ) {
+ <A HREF="<% $p %>view/svc_acct.cgi?<% $usersvc %>"><% $email %></A>
+% } else {
+ </i>(none)</i>
+% }
+ </TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TD ALIGN="right">Config lines</TD>
+ <TD BGCOLOR="#ffffff"><PRE><% join("\n", $svc_www->config) |h %></PRE></TD>
+ </TR>
+
+% foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+ <% $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)) %>
+% }
+
+</TABLE>
+
+<BR>
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('View customer services');
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_www = qsearchs({
+ 'select' => 'svc_www.*',
+ 'table' => 'svc_www',
+ 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN cust_main USING ( custnum ) ',
+ 'hashref' => { 'svcnum' => $svcnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql(
+ 'null_right' => 'View/link unlinked services'
+ ),
+}) or die "svc_www: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum, $display_custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+ $display_custnum = $cust_pkg->cust_main->display_custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $part_svc=qsearchs('part_svc',{'svcpart'=>$cust_svc->svcpart})
+ or die "svc_www: Unknown svcpart" . $cust_svc->svcpart;
+
+my $usersvc = $svc_www->usersvc;
+my $svc_acct = '';
+my $email = '';
+if ( $usersvc ) {
+ $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } )
+ or die "svc_www: Unknown usersvc $usersvc";
+ $email = $svc_acct->email;
+}
+
+my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } )
+ or die "svc_www: Unknown recnum ". $svc_www->recnum;
+
+my $www = $domain_record->zone;
+
+</%init>