diff options
Diffstat (limited to 'httemplate')
93 files changed, 4594 insertions, 680 deletions
diff --git a/httemplate/autohandler b/httemplate/autohandler new file mode 100644 index 000000000..2bd3adffd --- /dev/null +++ b/httemplate/autohandler @@ -0,0 +1,21 @@ +% $m->call_next; +<%init> + dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); +</%init> +<%filter> + +my $profile = ''; +if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { + + if ( lc($r->content_type) eq 'text/html' ) { + + $profile = '<PRE>'. ("\n"x4096). encode_entities(dbh->sprintProfile()). + #"\n\n". &sprintAutoProfile(). '</PRE>'; + "\n\n". '</PRE>'; + } + + dbh->{'private_profile'} = {}; +} + +s/(<\/BODY>[\s\n]*<\/HTML>[\s\n]*)$/$profile$1/i; +</%filter> diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index cff111ca4..2eef5bb91 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -1,15 +1,38 @@ <!-- mason kludge --> + <% -#Begin silliness -# -#use FS::UI::CGI; -#use FS::UI::agent; -# -#$ui = new FS::UI::agent; -#$ui->browse; -#exit; -#__END__ -#End silliness + + my %search; + if ( $cgi->param('showdisabled') + || !dbdef->table('agent')->column('disabled') ) { + %search = (); + } else { + %search = ( 'disabled' => '' ); + } + + #bad false laziness with search/cust_main.cgi (also needs fixing up for + #old mysql) + my $ncancelled = " + 0 < ( SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + AND ( cust_pkg.cancel IS NULL + OR cust_pkg.cancel = 0 + ) + ) + OR 0 = ( SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + ) + "; + + my $ncancelled_sth = dbh->prepare("SELECT COUNT(*) FROM cust_main + WHERE agentnum = ? + AND ( $ncancelled ) ") + or die dbh->errstr; + + my $total_sth = dbh->prepare("SELECT COUNT(*) FROM cust_main + WHERE agentnum = ? ") + or die dbh->errstr; + %> <%= header('Agent Listing', menubar( @@ -21,10 +44,20 @@ Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type).<BR><BR> <A HREF="<%= $p %>edit/agent.cgi"><I>Add a new agent</I></A><BR><BR> +<% if ( dbdef->table('agent')->column('disabled') ) { %> + <%= $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled agents</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled agents</a> )'; } + %> +<% } %> + <%= table() %> <TR> - <TH COLSPAN=2>Agent</TH> + <TH COLSPAN=<%= ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH> <TH>Type</TH> + <TH>Customers</TH> <TH><FONT SIZE=-1>Freq.</FONT></TH> <TH><FONT SIZE=-1>Prog.</FONT></TH> </TR> @@ -35,29 +68,44 @@ full offerings (via their type).<BR><BR> foreach my $agent ( sort { #$a->getfield('agentnum') <=> $b->getfield('agentnum') $a->getfield('agent') cmp $b->getfield('agent') -} qsearch('agent',{}) ) { - my($hashref)=$agent->hashref; - my($typenum)=$hashref->{typenum}; - my($agent_type)=qsearchs('agent_type',{'typenum'=>$typenum}); - my($atype)=$agent_type->getfield('atype'); - print <<END; +} qsearch('agent', \%search ) ) { + + $ncancelled_sth->execute($agent->agentnum) or die $ncancelled_sth->errstr; + my $num_ncancelled = $ncancelled_sth->fetchrow_arrayref->[0]; + + $total_sth->execute($agent->agentnum) or die $total_sth->errstr; + my $num_total = $total_sth->fetchrow_arrayref->[0]; + + my $num_cancelled = $num_total - $num_ncancelled; + + my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'. + 'agentnum='. $agent->agentnum; + +%> + <TR> - <TD><A HREF="${p}edit/agent.cgi?$hashref->{agentnum}"> - $hashref->{agentnum}</A></TD> - <TD><A HREF="${p}edit/agent.cgi?$hashref->{agentnum}"> - $hashref->{agent}</A></TD> - <TD><A HREF="${p}edit/agent_type.cgi?$typenum">$atype</A></TD> - <TD>$hashref->{freq}</TD> - <TD>$hashref->{prog}</TD> + <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>"> + <%= $agent->agentnum %></A></TD> +<% if ( dbdef->table('agent')->column('disabled') + && !$cgi->param('showdisabled') ) { %> + <TD><%= $agent->disabled ? 'DISABLED' : '' %></TD> +<% } %> + + <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>"> + <%= $agent->agent %></A></TD> + <TD><A HREF="<%=$p%>edit/agent_type.cgi?<%= $agent->typenum %>"><%= $agent->agent_type->atype %></A></TD> + <TD> + <FONT COLOR="#00CC00"><B><%= $num_ncancelled %></B></FONT> + <A HREF="<%= $cust_main_link %>&showcancelledcustomers=0">active</A> + <BR><FONT COLOR="#FF0000"><B><%= $num_cancelled %></B></FONT> + <A HREF="<%= $cust_main_link %>&showcancelledcustomers=1&cancelled=1">cancelled</A> + </TD> + <TD><%= $agent->freq %></TD> + <TD><%= $agent->prog %></TD> </TR> -END -} +<% } %> -print <<END; </TABLE> </BODY> </HTML> -END - -%> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi index 5a8438589..5473804e8 100755 --- a/httemplate/browse/agent_type.cgi +++ b/httemplate/browse/agent_type.cgi @@ -17,9 +17,11 @@ agents.<BR><BR> foreach my $agent_type ( sort { $a->getfield('typenum') <=> $b->getfield('typenum') } qsearch('agent_type',{}) ) { - my($hashref)=$agent_type->hashref; - my(@type_pkgs)=qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} }); - my($rowspan)=scalar(@type_pkgs); + my $hashref = $agent_type->hashref; + #more efficient to do this with SQL... + my @type_pkgs = grep { $_->part_pkg and ! $_->part_pkg->disabled } + qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} }); + my $rowspan = scalar(@type_pkgs); $rowspan = int($rowspan/2+0.5) ; print <<END; <TR> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index c2473c4c8..1e0e0880c 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -24,7 +24,7 @@ print '<BR><BR>'. &table(). <<END; <TH>Taxclass<BR><FONT SIZE=-1>(per-package classification)</FONT></TH> <TH>Tax name<BR><FONT SIZE=-1>(printed on invoices)</FONT></TH> <TH><FONT SIZE=-1>Tax</FONT></TH> - <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH> + <TH><FONT SIZE=-1>Exemption</TH> </TR> END @@ -54,7 +54,9 @@ END last if $hashref->{country} ne $regions[$i+$j]->country || $hashref->{state} ne $regions[$i+$j]->state || $hashref->{tax} != $regions[$i+$j]->tax - || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount; + || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount + || $hashref->{setuptax} ne $regions[$i+$j]->setuptax + || $hashref->{recurtax} ne $regions[$i+$j]->recurtax; } my $newsup=0; @@ -121,9 +123,13 @@ END print "</TD>"; print "<TD BGCOLOR=\"#ffffff\">$hashref->{tax}%</TD>". - '<TD BGCOLOR="#ffffff">$'. - sprintf("%.2f", $hashref->{exempt_amount} || 0). '</TD>'. - '</TR>'; + '<TD BGCOLOR="#ffffff">'; + print '$'. sprintf("%.2f", $hashref->{exempt_amount} ). + ' per month<BR>' + if $hashref->{exempt_amount} > 0; + print 'Setup fee<BR>' if $hashref->{setuptax} =~ /^Y$/i; + print 'Recurring fee<BR>' if $hashref->{recurtax} =~ /^Y$/i; + print '</TD></TR>'; } diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi index 608a58d0d..3420e97b6 100755 --- a/httemplate/browse/cust_pay_batch.cgi +++ b/httemplate/browse/cust_pay_batch.cgi @@ -1,10 +1,38 @@ <!-- mason kludge --> +<%= header("Pending credit card batch", menubar( 'Main Menu' => $p,)) %> + +<FORM ACTION="<%=$p%>misc/download-batch.cgi" METHOD="POST"> +Download batch in format <SELECT NAME="format"> +<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION> +</SELECT><INPUT TYPE="submit" VALUE="Download"></FORM> +<BR><BR> + +<FORM ACTION="<%=$p%>misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data"> +Upload results<BR> +Filename <INPUT TYPE="file" NAME="batch_results"><BR> +Format <SELECT NAME="format"> +<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION> +</SELECT><BR> +<INPUT TYPE="submit" VALUE="Upload"></FORM> +<BR> + <% + my $statement = "SELECT SUM(amount) from cust_pay_batch"; + my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement"; + $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; -print header("Pending credit card batch", menubar( - 'Main Menu' => $p, -# 'Add new referral' => "../edit/part_referral.cgi", -)), &table(), <<END; + my $c_statement = "SELECT COUNT(*) from cust_pay_batch"; + my $c_sth = dbh->prepare($c_statement) + or die dbh->errstr. "doing $c_statement"; + $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr; + my $cards = $c_sth->fetchrow_arrayref->[0]; +%> +<%= $cards %> credit card payments batched<BR> +$<%= sprintf("%.2f", $total) %> total in pending batch<BR> + +<BR> +<%= &table() %> <TR> <TH>#</TH> <TH><font size=-1>inv#</font></TH> @@ -14,39 +42,35 @@ print header("Pending credit card batch", menubar( <TH>Exp</TH> <TH>Amount</TH> </TR> -END - -foreach my $cust_pay_batch ( sort { - $a->getfield('paybatchnum') <=> $b->getfield('paybatchnum') -} qsearch('cust_pay_batch',{}) ) { -# my $date = time2str( "%a %b %e %T %Y", $queue->_date ); -# my $status = $hashref->{status}; -# if ( $status eq 'failed' || $status eq 'locked' ) { -# $status .= -# qq! ( <A HREF="$p/edit/cust_pay_batch.cgi?jobnum=$jobnum&action=new">retry</A> |!. -# qq! <A HREF="$p/edit/cust_pay_batch.cgi?jobnum$jobnum&action=del">remove </A> )!; -# } - my $cardnum = $cust_pay_batch->{cardnum}; - $cardnum =~ s/.{4}$/xxxx/; - print <<END; + +<% +foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } + qsearch('cust_pay_batch', {} ) +) { + my $cardnum = $cust_pay_batch->cardnum; + #$cardnum =~ s/.{4}$/xxxx/; + $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); + + $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my( $mon, $year ) = ( $2, $1 ); + $mon = "0$mon" if $mon < 10; + my $exp = "$mon/$year"; + +%> + <TR> - <TD>$cust_pay_batch->{paybatchnum}</TD> - <TD><A HREF="../view/cust_bill.cgi?$cust_pay_batch->{invnum}">$cust_pay_batch->{invnum}</TD> - <TD><A HREF="../view/cust_main.cgi?$cust_pay_batch->{custnum}">$cust_pay_batch->{custnum}</TD> - <TD>$cust_pay_batch->{last}, $cust_pay_batch->{last}</TD> - <TD>$cust_pay_batch->{payname}</TD> - <TD>$cardnum</TD> - <TD>$cust_pay_batch->{exp}</TD> - <TD align="right">\$$cust_pay_batch->{amount}</TD> + <TD><%= $cust_pay_batch->paybatchnum %></TD> + <TD><A HREF="../view/cust_bill.cgi?<%= $cust_pay_batch->invnum %>"><%= $cust_pay_batch->invnum %></TD> + <TD><A HREF="../view/cust_main.cgi?<%= $cust_pay_batch->custnum %>"><%= $cust_pay_batch->custnum %></TD> + <TD><%= $cust_pay_batch->get('last'). ', '. $cust_pay_batch->first %></TD> + <TD><%= $cust_pay_batch->payname %></TD> + <TD><%= $cardnum %></TD> + <TD><%= $exp %></TD> + <TD align="right">$<%= $cust_pay_batch->amount %></TD> </TR> -END -} +<% } %> -print <<END; </TABLE> </BODY> </HTML> -END - -%> diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 76662e0c9..79c57aefc 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -26,7 +26,7 @@ function part_export_areyousure(href) { <%= itable() %> <% my %opt = $part_export->options; foreach my $opt ( keys %opt ) { %> - <TR><TD><%= $opt %></TD><TD><%= $opt{$opt} %></TD></TR> + <TR><TD><%= $opt %></TD><TD><%= encode_entities($opt{$opt}) %></TD></TR> <% } %> </TABLE> </TD> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 7b9436cee..180f18263 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -12,7 +12,8 @@ my @part_pkg = qsearch('part_pkg', \%search ); my $total = scalar(@part_pkg); my $sortby; -my %num_active_cust_pkg; +my %num_active_cust_pkg = (); +my( $suspended_sth, $canceled_sth ) = ( '', '' ); if ( $cgi->param('active') ) { my $active_sth = dbh->prepare( 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. @@ -24,7 +25,21 @@ if ( $cgi->param('active') ) { $num_active_cust_pkg{$part_pkg->pkgpart} = $active_sth->fetchrow_arrayref->[0]; } - $sortby = \*active_cust_pkg_sort; + $sortby = sub { + $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart}; + }; + + $suspended_sth = dbh->prepare( + 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. + ' AND ( cancel IS NULL OR cancel = 0 )'. + ' AND susp IS NOT NULL AND susp != 0' + ) or die dbh->errstr; + + $canceled_sth = dbh->prepare( + 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. + ' AND cancel IS NOT NULL AND cancel != 0' + ) or die dbh->errstr; + } else { $sortby = \*pkgpart_sort; } @@ -63,8 +78,10 @@ print <<END; <TH><FONT SIZE=-1>Data</FONT></TH> <TH>Service</TH> <TH><FONT SIZE=-1>Quan.</FONT></TH> - </TR> END +print '<TH><FONT SIZE=-1>Primary</FONT></TH>' + if dbdef->table('pkg_svc')->column('primary_svc'); +print '</TR>'; foreach my $part_pkg ( sort $sortby @part_pkg ) { my($hashref)=$part_pkg->hashref; @@ -100,8 +117,19 @@ END print " <TD ROWSPAN=$rowspan>"; print '<FONT COLOR="#00CC00"><B>'. $num_active_cust_pkg{$hashref->{'pkgpart'}}. - qq!</B></FONT> <A HREF="${p}search/cust_pkg.cgi?magic=active;pkgpart=$hashref->{pkgpart}">active</A>!; - # suspended/cancelled + qq!</B></FONT> <A HREF="${p}search/cust_pkg.cgi?magic=active;pkgpart=$hashref->{pkgpart}">active</A><BR>!; + + $suspended_sth->execute( $part_pkg->pkgpart ) or die $suspended_sth->errstr; + my $num_suspended = $suspended_sth->fetchrow_arrayref->[0]; + print '<FONT COLOR="#FF9900"><B>'. $num_suspended. + qq!</B></FONT> <A HREF="${p}search/cust_pkg.cgi?magic=suspended;pkgpart=$hashref->{pkgpart}">suspended</A><BR>!; + + $canceled_sth->execute( $part_pkg->pkgpart ) or die $canceled_sth->errstr; + my $num_canceled = $canceled_sth->fetchrow_arrayref->[0]; + print '<FONT COLOR="#FF0000"><B>'. $num_canceled. + qq!</B></FONT> <A HREF="${p}search/cust_pkg.cgi?magic=canceled;pkgpart=$hashref->{pkgpart}">canceled</A>!; + + print '</TD>'; } print <<END; @@ -117,7 +145,13 @@ END my($part_svc) = qsearchs('part_svc',{'svcpart'=> $svcpart }); print $n,qq!<TD><A HREF="${p}edit/part_svc.cgi?$svcpart">!, $part_svc->getfield('svc'),"</A></TD><TD>", - $pkg_svc->getfield('quantity'),"</TD></TR>\n"; + $pkg_svc->getfield('quantity'),"</TD>"; + if ( dbdef->table('pkg_svc')->column('primary_svc') ) { + print '<TD>'; + print 'PRIMARY' if $pkg_svc->primary_svc =~ /^Y/i; + print '</TD>'; + } + print "</TR>\n"; $n="<TR>"; } @@ -132,13 +166,8 @@ print <<END; </HTML> END - sub pkgpart_sort { $a->pkgpart <=> $b->pkgpart; } -sub active_cust_pkg_sort { - $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart}; -} - %> diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi index 084c21bd0..3f59abcf5 100755 --- a/httemplate/browse/part_referral.cgi +++ b/httemplate/browse/part_referral.cgi @@ -8,31 +8,57 @@ Where a customer heard about your service. Tracked for informational purposes. <A HREF="<%= $p %>edit/part_referral.cgi"><I>Add a new advertising source</I></A> <BR><BR> +<% + my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); + my %past; + tie %past, 'Tie::IxHash', + 'Today' => 0, + 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days + 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days + 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 29days + 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 29days + 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days + 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days + 'Total' => $today, + ; + + my $sth = dbh->prepare("SELECT COUNT(*) FROM h_cust_main + WHERE history_action = 'insert' + AND refnum = ? + AND history_date > ? ") + or die dbh->errstr; +%> + <%= table() %> <TR> - <TH COLSPAN=2>Advertising source</TH> + <TH COLSPAN=2 ROWSPAN=2>Advertising source</TH> + <TH COLSPAN=<%= scalar(keys %past) %>>Customers</TH> +</TR> +<% for my $period ( keys %past ) { %> + <TH><FONT SIZE=-1><%= $period %></FONT></TH> +<% } %> </TR> <% foreach my $part_referral ( sort { $a->getfield('refnum') <=> $b->getfield('refnum') } qsearch('part_referral',{}) ) { - my($hashref)=$part_referral->hashref; - print <<END; +%> <TR> - <TD><A HREF="${p}edit/part_referral.cgi?$hashref->{refnum}"> - $hashref->{refnum}</A></TD> - <TD><A HREF="${p}edit/part_referral.cgi?$hashref->{refnum}"> - $hashref->{referral}</A></TD> + <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>"> + <%= $part_referral->refnum %></A></TD> + <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>"> + <%= $part_referral->referral %></A></TD> + <% for my $period ( values %past ) { + $sth->execute($part_referral->refnum, $today-$period) + or die $sth->errstr; + my $number = $sth->fetchrow_arrayref->[0]; + %> + <TD ALIGN="right"><%= $number %></TD> + <% } %> </TR> -END - -} +<% } %> -print <<END; </TABLE> </BODY> </HTML> -END - -%> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 3fece2925..ef0de13cc 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -13,6 +13,20 @@ my @part_svc = qsearch('part_svc', \%search ); my $total = scalar(@part_svc); +my %num_active_cust_svc = (); +if ( $cgi->param('active') ) { + my $active_sth = dbh->prepare( + 'SELECT COUNT(*) FROM cust_svc WHERE svcpart = ?' + ) or die dbh->errstr; + foreach my $part_svc ( @part_svc ) { + $active_sth->execute($part_svc->svcpart) or die $active_sth->errstr; + $num_active_cust_svc{$part_svc->svcpart} = + $active_sth->fetchrow_arrayref->[0]; + } + @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=> + $num_active_cust_svc{$a->svcpart} } @part_svc; +} + %> <%= header('Service Definition Listing', menubar( 'Main Menu' => $p) ) %> @@ -45,6 +59,9 @@ function part_export_areyousure(href) { <TR> <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Service</TH> <TH>Table</TH> +<% if ( $cgi->param('active') ) { %> + <TH><FONT SIZE=-1>Customer<BR>Services</FONT></TH> +<% } %> <TH>Export</TH> <TH>Field</TH> <TH COLSPAN=2>Modifier</TH> @@ -75,6 +92,11 @@ function part_export_areyousure(href) { <%= $hashref->{svc} %></A></TD> <TD ROWSPAN=<%= $rowspan %>> <%= $hashref->{svcdb} %></TD> +<% if ( $cgi->param('active') ) { %> + <TD ROWSPAN=<%= $rowspan %>> + <FONT COLOR="#00CC00"><B><%= $num_active_cust_svc{$hashref->{svcpart}} %></B></FONT> <A HREF="<%=$p%>search/<%= $hashref->{svcdb} %>.cgi?svcpart=<%= $hashref->{svcpart} %>">active</A> + </TD> +<% } %> <TD ROWSPAN=<%= $rowspan %>><%= itable() %> <% # my @part_export = diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi index a9ec6fd61..feee4ecaa 100644 --- a/httemplate/browse/router.cgi +++ b/httemplate/browse/router.cgi @@ -18,7 +18,7 @@ my $p2 = popurl(2); <TD><B>Router name</B></TD> <TD><B>Address block(s)</B></TD> </TR> -<% foreach $router (sort {$a->routernum <=> $b->routernum} @router) { +<% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) { my @addr_block = $router->addr_block; %> <TR> diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi index 8d35cb56a..44cda81ad 100755 --- a/httemplate/browse/svc_acct_pop.cgi +++ b/httemplate/browse/svc_acct_pop.cgi @@ -1,4 +1,9 @@ <!-- mason kludge --> +<% + my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct + WHERE popnum = ? ") + or die dbh->errstr; +%> <%= header('Access Number Listing', menubar( 'Main Menu' => $p )) %> Points of Presence<BR><BR> <A HREF="<%= $p %>edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A><BR><BR> @@ -10,6 +15,7 @@ Points of Presence<BR><BR> <TH>Area code</TH> <TH>Exchange</TH> <TH>Local</TH> + <TH>Accounts</TH> </TR> <% @@ -18,32 +24,40 @@ foreach my $svc_acct_pop ( sort { $a->state cmp $b->state || $a->city cmp $b->city || $a->ac <=> $b->ac || $a->exch <=> $b->exch || $a->loc <=> $b->loc } qsearch('svc_acct_pop',{}) ) { - my($hashref)=$svc_acct_pop->hashref; - print <<END; + + my $svc_acct_pop_link = $p . 'edit/svc_acct_pop.cgi?'. $svc_acct_pop->popnum; + + $accounts_sth->execute($svc_acct_pop->popnum) or die $accounts_sth->errstr; + my $num_accounts = $accounts_sth->fetchrow_arrayref->[0]; + + my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='. $svc_acct_pop->popnum; + +%> <TR> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{popnum}</A></TD> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{city}</A></TD> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{state}</A></TD> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{ac}</A></TD> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{exch}</A></TD> - <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}"> - $hashref->{loc}</A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->popnum %></A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->city %></A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->state %></A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->ac %></A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->exch %></A></TD> + <TD><A HREF="<%= $svc_acct_pop_link %>"> + <%= $svc_acct_pop->loc %></A></TD> + <TD> + <FONT COLOR="#00CC00"><B><%= $num_accounts %></B></FONT> + <% if ( $num_accounts ) { %><A HREF="<%= $svc_acct_link %>"><% } %> + active + <% if ( $num_accounts ) { %></A><% } %> + </TD> </TR> -END +<% } %> -} - -print <<END; <TR> </TR> </TABLE> </BODY> </HTML> -END -%> diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html index c78a87f04..1d6f8c4fc 100644 --- a/httemplate/docs/billing.html +++ b/httemplate/docs/billing.html @@ -11,7 +11,7 @@ <li>Credit card decline alerts: Customize the <a href="../config/config.cgi#declinetemplate">declinetemplate</a> configuration option and set the <a href="../config/config.cgi#emaildecline">emaildecline</a> configuration option. <li>Optional: Invoice template customization <ul> - <li>See the <a href="http://search.cpan.org/doc/MJD/Text-Template-1.42/Template.pm">Text::Template</a> documentation for details on the substitution language. + <li>See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the substitution language. <li>You <b>must</b> call the invoice_lines() function at least once - pass it a number of lines, and it returns a list of array references, each of two elements: a service description column, and a price column. Alternatively, call invoice_lines() with no arguments, and pagination will be disabled - all invoice line items will print on one page, with no padding (recommended for email invoices). <li>In addition, the following variables are available: <ul> diff --git a/httemplate/docs/cvv2.html b/httemplate/docs/cvv2.html new file mode 100644 index 000000000..fe8a17f6f --- /dev/null +++ b/httemplate/docs/cvv2.html @@ -0,0 +1,25 @@ +<HTML> + <HEAD> + <TITLE> + CVV2 information + </TITLE> + </HEAD> + <BODY BGCOLOR="#e8e8e8"> + The CVV2 number (also called CVC2 or CID) is a three- or four-digit + security code used to reduce credit card fraud.<BR><BR> + <TABLE BORDER=0 CELLSPACING=4> + <TR> + <TH>Visa / MasterCard / Discover</TH> + <TH>American Express</TH> + </TR> + <TR> + <TD> + <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="../images/cvv2.png"> + </TD> + <TD> + <IMG BORDER=0 ALT="American Express" SRC="../images/cvv2_amex.png"> + </TD> + </TABLE> + <CENTER><A HREF="javascript:close()">(close window)</A></CENTER> + </BODY> +</HTML> diff --git a/httemplate/docs/ieak.html b/httemplate/docs/ieak.html new file mode 100644 index 000000000..00c53423c --- /dev/null +++ b/httemplate/docs/ieak.html @@ -0,0 +1,75 @@ +<pre> +this is incomplete +mostly it should be merged into signup.html and fs_signup/ieak.template + +- download and install the IEAK from + http://www.microsoft.com/windows/ieak/default.asp + +- Good examples may be found in + C:\Program Files\IEAK\toolkit\isp\server\ICW\signup\perl\signup08.pl + C:\Program Files\IEAK\toolkit\isp\server\ICW\reconfig\perl\reconfig04.pl + C:\Program Files\IEAK6\toolkit\isp\servless\basic\sample.ins + C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4567.ins + C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4568.ins + C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7890.ins + C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7891.ins + +- Full documentation on all the settings available in .INS files is + avaialble under Program Files | Microsoft IEAK 6 | IEAK Help + | Reference | Internet Settings (.ins) Files + +- Freeside will make the following substitutions before sending the file + to the user: + + { $ac } - area code of selected POP + { $exch } - exchange of selected POP + { $loc } - local part of selected POP + { $username } + { $password } + { $email_name } - first and last name + { $pkg } - package name + +- Simple example follows: + +[Entry] +Entry Name = IEAK Sample +[Phone] +Dial_As_Is = No +Phone_Number = { $exch }{ $loc } +Area_Code = { $ac } +Country_Code = 1 +Country_Id = 1 +[Server] +Type = PPP +SW_Compress = Yes +PW_Encrypt = Yes +Negotiate_TCP/IP = Yes +Disable_LCP = No +[TCP/IP] +Specity_IP_Address = No +Specity_Server_Address = No +IP_Header_Compress = Yes +Gateway_On_Remote = Yes +[User] +Name = { $username } +Passowrd = { $password } +Display_Password = Yes +[Internet_Mail] +Email_Name = { $email_name } +Email_Address = { $username }@example.com +POP_Server = mail.example.com +POP_Server_Port_Number = 110 +POP_Logon_Password = { $password } +SMTP_Server = mail.example.com +SMTP_Server_Port_Number = 25 +Install_Mail = 1 +[URL] +Help_Page = http://www.ieaksample.net/helpdesk +Home_Page = http://www.ieaksample.net +Search_Page = http://www.ieaksample,net/search +[Favorites] +IEAK Sample \\ IEAK Sample Home Page.url = http://acme.ieaksample.net/ +[Branding] +Window_Title = Internet Explorer from Acme Internet Services + +</pre> diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html index b57b06feb..648cb985b 100644 --- a/httemplate/docs/index.html +++ b/httemplate/docs/index.html @@ -9,6 +9,7 @@ <li><a href="upgrade7.html">Upgrading from 1.3.0 to 1.3.1</a> <li><a href="upgrade8.html">Upgrading from 1.3.1 to 1.4.0</a> <li><a href="upgrade9.html">Upgrading from 1.4.0 to 1.4.1</a> + <li><a href="upgrade-1.4.2.html">Upgrading from 1.4.1 to 1.4.2</a> <li><a href="upgrade10.html">Upgrading from 1.4.1 (or 1.4.2?) to 1.5.0</a> <!-- <li><a href="config.html">Configuration files</a> diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html index 54614ccb3..ed306f2d3 100644 --- a/httemplate/docs/install.html +++ b/httemplate/docs/install.html @@ -3,9 +3,10 @@ </head> <body> <h1>Installation</h1> +<i>Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.</i><br><br> Before installing, you need: <ul> - <li><a href="http://www.perl.com/">Perl</a> Don't enable experimental features like threads or the PerlIO abstraction layer. + <li><a href="http://www.perl.com/">Perl</a> <li><a href="http://www.apache.org">Apache</a> (<a href="http://www.modssl.org/">mod_ssl</a> or <a href="http://www.apache-ssl.org">Apache-SSL</a> highly recommended) <li><a href="http://perl.apache.org/">mod_perl</a> (if compiling your own mod_perl, make sure you set the <a href="http://perl.apache.org/guide/install.html#EVERYTHING">EVERYTHING</a>=1 compile-time option) <li><a href="http://www.openssh.com/">SSH</a> (<a href="http://www.openssh.com//">OpenSSH</a> is recommended. SSH Communications Security <a href="http://www.ssh.com/products/ssh/download.cfm">commercial SSH version 3</a> has been reported incompatible with Freeside.) @@ -17,7 +18,7 @@ Before installing, you need: <!-- <li>MySQL has been reported to work. --> <b>MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are not supported</b>. If you want to use MySQL, you <b>must</b> use one of the new <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/B/D/BDB.html">BDB</a> or <a href="http://www.mysql.com/doc/I/n/InnoDB.html">InnoDB</a>, and set it as the default table type using the <code>--default-table-type=BDB</code> or <code>--default-table-type=InnoDB</code> <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Command-line_options">mysqld command-line option</a> or by setting <code>default-table-type=BDB</code> or <code>default-table-type=InnoDB</code> in the <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Option_files">my.cnf option file</a>. </ul> - <li>Perl modules (<a href="http://theoryx5.uwinnipeg.ca/CPAN/perl/CPAN.html">CPAN</a> will query, download and build perl modules automatically) + <li>Perl modules (<a href="http://search.cpan.org/~andk/CPAN/lib/CPAN.pm">CPAN</a> will query, download and build perl modules automatically) <ul> <!-- <li><a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a> <li><a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) --> @@ -130,22 +131,23 @@ cp htetc/global.asa /usr/local/etc/freeside/asp-global/global.asa <font size="-1"><pre> PerlModule Apache::ASP <Directory /usr/local/apache/htdocs/freeside-asp> -<Files ~ (\.cgi)> -AddHandler perl-script .cgi +<Files ~ (\.cgi|\.html)> +SetHandler perl-script PerlHandler Apache::ASP </Files> <Perl> $MLDBM::RemoveTaint = 1; </Perl> PerlSetVar Global /usr/local/etc/freeside/asp-global/ -PerlSetVar Debug 2 +PerlSetVar Debug 2 +PerlSetVar RequestBinaryRead Off </Directory> </pre></font> </ul></td> <td><ul> <li>Run <tt>make masondocs</tt> <li>Copy <tt>masondocs/</tt> to your web server's document space. (For example: <tt>/usr/local/apache/htdocs/freeside-mason</tt>) - <li>Copy <tt>htetc/handler.pl</tt> to <tt>/usr/local/etc/freeside</tt> (use htetc/handler.pl-1.0x for Mason versions before 1.10). + <li>Copy <tt>htetc/handler.pl</tt> to <tt>/usr/local/etc/freeside</tt> <li>Edit <tt>handler.pl</tt> and: <ul> <li> set an appropriate <tt>comp_root</tt>, such as <tt>/usr/local/apache/htdocs/freeside-mason</tt> @@ -156,8 +158,8 @@ PerlSetVar Debug 2 <font size="-1"><pre> PerlModule HTML::Mason <Directory /usr/local/apache/htdocs/freeside-mason> -<Files ~ (\.cgi)> -AddHandler perl-script .cgi +<Files ~ (\.cgi|.html)> +SetHandler perl-script PerlHandler HTML::Mason </Files> <Perl> diff --git a/httemplate/docs/legacy.html b/httemplate/docs/legacy.html index 678780962..94efe53af 100755 --- a/httemplate/docs/legacy.html +++ b/httemplate/docs/legacy.html @@ -3,7 +3,7 @@ </head> <body> <h1>Importing legacy data</h1> -<font size="+2">In most cases, legacy data import all cases will require writing custom code to deal with your particular legacy data. The example scripts here will not work "out-of-the-box". Importing your legacy data will most probably involve some hacking on the example scripts noted below. Contributions to the import process are welcome.</font> +<font size="+2">In almost all cases, legacy data import will require writing custom code to deal with your particular legacy data. The example scripts here will probably <b>not</b> work "out-of-the-box", and are provided <b>as a starting point only</b>.</font> <br><br><i>Some import scripts may require installation of the <a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a> and <a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) modules.</i><br> <ul> <li><a name="bind">bin/bind.import</a> - Import domain information from BIND named diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html index a59755e76..b38031777 100644 --- a/httemplate/docs/schema.html +++ b/httemplate/docs/schema.html @@ -12,6 +12,9 @@ <li>typenum - <a href="#agent_type">agent type</a> <li>prog - (unimplemented) <li>freq - (unimplemented) + <li>disabled - Disabled flag, empty or 'Y' + <li>username - Username for the Agent interface + <li>_password - Password for the Agent interface </ul> <li><a name="agent_type" href="man/FS/agent_type.html">agent_type</a> - Agent types define groups of packages that you can then assign to particular agents. <ul> @@ -118,6 +121,7 @@ <li><i>ship_fax</i> <li>payby - CARD, DCHK, CHEK, DCHK, LECB, BILL, or COMP <li>payinfo - card number, P.O.#, or comp issuer + <li>paycvv - Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card <li>paydate - expiration date <li>payname - billing name (name on card) <li>tax - tax exempt, Y or null @@ -142,6 +146,8 @@ <li>taxclass <li>exempt_amount <li>taxname - if defined, printed on invoices instead of "Tax" + <li>setuptax - if 'Y', this tax does not apply to setup fees + <li>recurtax - if 'Y', this tax does not apply to recurring fees </ul> <li><a name="cust_tax_exempt" href="man/FS/cust_tax_exempt.html">cust_tax_exempt</a> - Tax exemption record <ul> @@ -277,6 +283,7 @@ <li>pkgpart - <a href="#part_pkg">Package definition</a> <li>svcpart - <a href="#part_svc">Service definition</a> <li>quantity - quantity of this service that this package includes + <li>primary_svc - blank or Y: primary service </ul> <li><a name="export_svc" href="man/FS/export_svc.html">export_svc</a> <ul> @@ -366,8 +373,9 @@ <ul> <li>svcnum - <a href="#cust_svc">primary key</a> <li>srcsvc - <a href="#svc_acct">svcnum of the source of this forward</a> + <li>src - literal source (username or full email address) <li>dstsvc - <a href="#svc_acct">svcnum of the destination of this forward</a> - <li>dst - foreign destination (email address) - forward not local to freeside + <li>dst - literal destination (username or full email address) </ul> <li><a name="domain_record" href="man/FS/domain_record.html">domain_record</a> - Domain zone detail <ul> diff --git a/httemplate/docs/signup.html b/httemplate/docs/signup.html index 5168f47d6..97d7aa794 100644 --- a/httemplate/docs/signup.html +++ b/httemplate/docs/signup.html @@ -12,7 +12,7 @@ webserver. On this machine, install: <li><a href="http://search.cpan.org/search?dist=Text-Template">Text::Template</a> <li><a href="http://search.cpan.org/search?dist=Storable">Storable</a> <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a> - <li><a href="http://www.sisd.com/useragent">HTTP::Headers::UserAgent</a> (version 2.0 or higher; not yet indexed correctly on CPAN) + <li><a href="http://search.cpan.org/search?dist=HTTP-BrowserDetect">HTTP::BrowserDetect</a> <li><a href="man/FS/SignupClient.html">FS::SignupClient</a> (copy the fs_signup/FS-SignupClient directory to the external machine, then: perl Makefile.PL; make; make install) </ul> @@ -36,9 +36,7 @@ Then: Optional: <ul> <li>If you create a <b>/usr/local/freeside/ieak.template</b> file on the external machine, it will be sent to IE users with MIME type <i>application/x-Internet-signup</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available. - (an example file is included as <b>fs_signup/ieak.template</b>) See the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information. - <li>If you create a <b>/usr/local/freeside/cck.template</b> file on the external machine, the variables defined will be sent to Netscape users with MIME type <i>application/x-netscape-autoconfigure-dialer-v2</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available. - (an example file is included as <b>fs_signup/cck.template</b>). See the <a href="http://help.netscape.com/products/client/mc/acctproc4.html">Netscape documentation</a> for more information. + (an example file is included as <b>fs_signup/ieak.template</b>) See the section on <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/INS.HTM">internet settings files</a> in the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information. <li>If you create a <b>/usr/local/freeside/success.html</b> file on the external machine, it will be used as the success HTML page. Although template substiutions are available, a regular HTML file will work fine here, unlike signup.html. An example file is included as <b>fs_signup/FS-SignupClient/cgi/success.html</b> <li>Variable substitutions available in <b>ieak.template</b>, <b>cck.template</b> and <b>success.html</b>: <ul> diff --git a/httemplate/docs/upgrade-1.4.2.html b/httemplate/docs/upgrade-1.4.2.html new file mode 100644 index 000000000..1feaa803a --- /dev/null +++ b/httemplate/docs/upgrade-1.4.2.html @@ -0,0 +1,20 @@ +<head> + <title>Upgrading to 1.4.2</title> +</head> +<body> +<h1>Upgrading to 1.4.2 from 1.4.1</h1> +<ul> + <li>If migrating from less than 1.4.1, see these <a href="upgrade9.html">instructions</a> first. + <li>Back up your data and current Freeside installation. + <li>Install <a href="http://search.cpan.org/search?dist=Locale-SubCountry">Locale::SubCountry</a> + <li>Install <a href="http://search.cpan.org/search?dist=IPC-ShareLite">IPC::ShareLite</a> + <li>Install <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML::Widgets::SelectLayers</a> 0.03. + <li>CGI.pm minimum version 2.47 is required. You will probably need to install a current CGI.pm from CPAN if you are using Perl 5.005 or earlier. + <li>If using Apache::ASP, add <code>PerlSetVar RequestBinaryRead Off</code> to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55. + <li>Run <code>make aspdocs</code> or <code>make masondocs</code>. + <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space. + <li>Run <code>make install-perl-modules</code>. + <li>The signup server and password server are deprecated in 1.4.2. Their functionality has been incorperated into the self-service server. Edit or reinstall your init script, and set the "signup_server-default_agentnum" and "signup_server-default_refnum" configuration options. The FS::SignupClient interface is still available as a compatibility wrapper, so you should be able to continue to use your current signup.cgi. + <li>Optional: To use typeset invoices, install tetex and ghostscript, and copy conf/invoice_latex, conf/invoice_latexnotes, and conf/invoice_latexfooter to /usr/local/etc/freeside/conf.<datasrc>/ + <li>Restart Apache and freeside-queued. +</body> diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index 4c2c17b85..774babb9e 100644 --- a/httemplate/docs/upgrade10.html +++ b/httemplate/docs/upgrade10.html @@ -1,9 +1,9 @@ <pre> this is incomplete -install DBIx::DBSchema 0.21 +install DBIx::DBSchema 0.22 -install NetAddr::IP and Chart::Base +install NetAddr::IP, Chart::Base, IPC::ShareLite and Locale::SubCountry CREATE TABLE cust_bill_pkg_detail ( detailnum serial, @@ -62,14 +62,112 @@ CREATE TABLE svc_broadband ( PRIMARY KEY (svcnum) ); -DELETE INDEX cust_bill_pkg1; +CREATE TABLE acct_snarf ( + snarfnum serial, + svcnum int NOT NULL, + machine varchar(255) NULL, + protocol varchar(80) NULL, + username varchar(80) NULL, + _password varchar(80) NULL, + PRIMARY KEY (snarfnum) +); +CREATE INDEX acct_snarf1 ON acct_snarf ( svcnum ); + +CREATE TABLE svc_external ( + svcnum int NOT NULL, + id int NOT NULL, + title varchar(80), + PRIMARY KEY (svcnum) +); + +CREATE TABLE part_pkg_temp ( + pkgpart serial NOT NULL, + pkg varchar(80) NOT NULL, + "comment" varchar(80) NOT NULL, + setup text NULL, + freq varchar(80) NOT NULL, + recur text NULL, + setuptax char(1) NULL, + recurtax char(1) NULL, + plan varchar(80) NULL, + plandata text NULL, + disabled char(1) NULL, + taxclass varchar(80) NULL, + PRIMARY KEY (pkgpart), +); +INSERT INTO part_pkg_temp SELECT * from part_pkg; +DROP TABLE part_pkg; +ALTER TABLE part_pkg_temp RENAME TO part_pkg; +ALTER TABLE part_pkg DROP CONSTRAINT part_pkg_temp_pkey; +ALTER TABLE part_pkg ADD PRIMARY KEY (pkgpart); +CREATE INDEX part_pkg1 ON part_pkg(disabled); +select setval('public.part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); #? + +CREATE TABLE h_part_pkg_temp ( + historynum serial NOT NULL, + history_date int, + history_user varchar(80) NOT NULL, + history_action varchar(80) NOT NULL, + pkgpart int NOT NULL, + pkg varchar(80) NOT NULL, + "comment" varchar(80) NOT NULL, + setup text NULL, + freq varchar(80) NOT NULL, + recur text NULL, + setuptax char(1) NULL, + recurtax char(1) NULL, + plan varchar(80) NULL, + plandata text NULL, + disabled char(1) NULL, + taxclass varchar(80) NULL, + PRIMARY KEY (historynum) +); +INSERT INTO h_part_pkg_temp SELECT * from h_part_pkg; +DROP TABLE h_part_pkg; +ALTER TABLE h_part_pkg_temp RENAME TO h_part_pkg; +ALTER TABLE h_part_pkg DROP CONSTRAINT h_part_pkg_temp_pkey; +ALTER TABLE h_part_pkg ADD PRIMARY KEY (historynum); +CREATE INDEX h_part_pkg1 ON h_part_pkg(disabled); +select setval('public.h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) ); + + +DROP INDEX cust_bill_pkg1; ALTER TABLE cust_bill_pkg ADD itemdesc varchar(80) NULL; ALTER TABLE h_cust_bill_pkg ADD itemdesc varchar(80) NULL; ALTER TABLE cust_main_county ADD taxname varchar(80) NULL; ALTER TABLE h_cust_main_county ADD taxname varchar(80) NULL; +ALTER TABLE cust_main_county ADD setuptax char(1) NULL; +ALTER TABLE h_cust_main_county ADD setuptax char(1) NULL; +ALTER TABLE cust_main_county ADD recurtax char(1) NULL; +ALTER TABLE h_cust_main_county ADD recurtax char(1) NULL; ALTER TABLE cust_pkg ADD last_bill int NULL; ALTER TABLE h_cust_pkg ADD last_bill int NULL; +ALTER TABLE agent ADD disabled char(1) NULL; +ALTER TABLE h_agent ADD disabled char(1) NULL; +ALTER TABLE agent ADD username varchar(80) NULL; +ALTER TABLE h_agent ADD username varchar(80) NULL; +ALTER TABLE agent ADD _password varchar(80) NULL; +ALTER TABLE h_agent ADD _password varchar(80) NULL; +ALTER TABLE cust_main ADD paycvv varchar(4) NULL; +ALTER TABLE h_cust_main ADD paycvv varchar(4) NULL; +ALTER TABLE part_referral ADD disabled char(1) NULL; +ALTER TABLE h_part_referral ADD disabled char(1) NULL; +CREATE INDEX part_referral1 ON part_referral ( disabled ); +ALTER TABLE pkg_svc ADD primary_svc char(1) NULL; +ALTER TABLE h_pkg_svc ADD primary_svc char(1) NULL; +ALTER TABLE svc_forward ADD src varchar(255) NULL; +ALTER TABLE h_svc_forward ADD src varchar(255) NULL; + +On recent Pg versions: + +ALTER TABLE svc_forward ALTER COLUMN srcsvc DROP NOT NULL; +ALTER TABLE h_svc_forward ALTER COLUMN srcsvc DROP NOT NULL; +ALTER TABLE svc_forward ALTER COLUMN dstsvc DROP NOT NULL; +ALTER TABLE h_svc_forward ALTER COLUMN dstsvc DROP NOT NULL; + +Or on Pg versions that don't support DROP NOT NULL (tested only on 7.2 so far): +UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'srcsvc' OR attname = 'dstsvc' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'svc_forward' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_svc_forward' ) ); dump database, edit: - cust_main: increase otaker from 8 to 32 @@ -90,15 +188,17 @@ optionally: CREATE INDEX cust_main9 ON cust_main ( ship_daytime ); CREATE INDEX cust_main10 ON cust_main ( ship_night ); CREATE INDEX cust_main11 ON cust_main ( ship_fax ); + CREATE INDEX agent2 ON agent ( disabled ); + CREATE INDEX part_bill_event2 ON part_bill_event ( disabled ); serial columns mandatory again: dbdef-create username -create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband +create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband acct_snarf svc_external dbdef-create username - +apache - fix <Files> sections to include .html also </pre> diff --git a/httemplate/docs/upgrade9.html b/httemplate/docs/upgrade9.html index 24d1cce42..6a8fd965d 100644 --- a/httemplate/docs/upgrade9.html +++ b/httemplate/docs/upgrade9.html @@ -21,6 +21,8 @@ CREATE INDEX part_pkg1 ON part_pkg ( disabled ); CREATE INDEX part_svc1 ON part_svc ( disabled ); CREATE INDEX cust_bill2 ON cust_bill ( _date ); </pre> - <li>If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump, and run dbdef-create + <li>If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump. + <li>If you will be doing bind exports you should make additional changes to your database. Follow the directions above to dump the database and change the reczone and recdata fields in the domain_record and h_domain_record tables from varchar(80) to varchar(255). + <li>If you made changes to your db schema from a dump as listed above run dbdef-create. <li>Restart Apache and freeside-queued. </body> diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi index e44acba3c..d9b7579f6 100755 --- a/httemplate/edit/REAL_cust_pkg.cgi +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -1,6 +1,6 @@ <!-- mason kludge --> <% -# <!-- $Id: REAL_cust_pkg.cgi,v 1.5 2003-04-01 01:22:24 ivan Exp $ --> +# <!-- $Id: REAL_cust_pkg.cgi,v 1.7 2003-11-19 12:21:09 ivan Exp $ --> my $error =''; my $pkgnum = ''; @@ -30,6 +30,15 @@ print header('Package Edit'); #, menubar( # 'Main Menu' => popurl(2) #)); +%> + + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + +<% + #print info my($susp,$cancel,$expire)=( $cust_pkg->getfield('susp'), @@ -45,6 +54,9 @@ print '<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">', print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! if $error; +#my $format = "%c %z (%Z)"; +my $format = "%m/%d/%Y %T %z (%Z)"; + print ntable("#cccccc",2), '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">', $pkgnum, '</TD></TR>', @@ -55,23 +67,29 @@ print ntable("#cccccc",2), '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">', $otaker, '</TD></TR>', '<TR><TD ALIGN="right">Setup date</TD><TD>'. - '<INPUT TYPE="text" NAME="setup" SIZE=32 VALUE="', - ( $setup ? time2str("%c %z (%Z)",$setup) : "" ), '"></TD></TR>'; + '<INPUT TYPE="text" NAME="setup" SIZE=32 ID="setup_text" VALUE="', + ( $setup ? time2str($format, $setup) : "" ), '">'. + ' <IMG SRC="../images/calendar.png" ID="setup_button" STYLE="cursor: pointer" TITLE="Select date">'. + '</TD></TR>'; print '<TR><TD ALIGN="right">Last bill date</TD><TD>', - '<INPUT TYPE="text" NAME="last_bill" SIZE=32 VALUE="', + '<INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="', ( $cust_pkg->last_bill - ? time2str("%c %z (%Z)", $cust_pkg->last_bill) + ? time2str($format, $cust_pkg->last_bill) : "" ), - '"></TD></TR>' + '">'. + ' <IMG SRC="../images/calendar.png" ID="last_bill_button" STYLE="cursor: pointer" TITLE="Select date">'. + '</TD></TR>' if $cust_pkg->dbdef_table->column('last_bill'); print '<TR><TD ALIGN="right">Next bill date</TD><TD>', - '<INPUT TYPE="text" NAME="bill" SIZE=32 VALUE="', - ( $bill ? time2str("%c %z (%Z)",$bill) : "" ), '"></TD></TR>'; + '<INPUT TYPE="text" NAME="bill" SIZE=32 ID="bill_text" VALUE="', + ( $bill ? time2str($format, $bill) : "" ), '">'. + ' <IMG SRC="../images/calendar.png" ID="bill_button" STYLE="cursor: pointer" TITLE="Select date">'. + '</TD></TR>'; print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">', - time2str("%D",$susp), '</TD></TR>' + time2str($format, $susp), '</TD></TR>' if $susp; #print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">', @@ -79,18 +97,34 @@ print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">', # if $expire; print '<TR><TD ALIGN="right">Expiration date'. '</TD><TD>', - '<INPUT TYPE="text" NAME="expire" SIZE=32 VALUE="', - ( $expire ? time2str("%c %z (%Z)",$expire) : "" ), '">'. + '<INPUT TYPE="text" NAME="expire" SIZE=32 ID="expire_text" VALUE="', + ( $expire ? time2str($format, $expire) : "" ), '">'. + ' <IMG SRC="../images/calendar.png" ID="expire_button" STYLE="cursor: pointer" TITLE="Select date">'. '<BR><FONT SIZE=-1>(will <b>cancel</b> this package'. ' when the date is reached)</FONT>'. '</TD></TR>'; print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">', - time2str("%D",$cancel), '</TD></TR>' + time2str($format, $cancel), '</TD></TR>' if $cancel; %> </TABLE> +<SCRIPT TYPE="text/javascript"> +<% + my @cal = qw( setup bill expire ); + push @cal, 'last_bill' + if $cust_pkg->dbdef_table->column('last_bill'); + foreach my $cal (@cal) { +%> + Calendar.setup({ + inputField: "<%= $cal %>_text", + ifFormat: "%m/%d/%Y", + button: "<%= $cal %>_button", + align: "BR" + }); +<% } %> +</SCRIPT> <BR><INPUT TYPE="submit" VALUE="Apply Changes"> </FORM> </BODY> diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi index 449456cdd..8a1cb2ae1 100755 --- a/httemplate/edit/agent.cgi +++ b/httemplate/edit/agent.cgi @@ -16,59 +16,64 @@ if ( $cgi->param('error') ) { my $action = $agent->agentnum ? 'Edit' : 'Add'; my $hashref = $agent->hashref; -print header("$action Agent", menubar( +%> + +<%= header("$action Agent", menubar( 'Main Menu' => $p, 'View all agents' => $p. 'browse/agent.cgi', -)); +)) %> -print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), - "</FONT>" - if $cgi->param('error'); +<% if ( $cgi->param('error') ) { %> +<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> +<% } %> -print '<FORM ACTION="', popurl(1), 'process/agent.cgi" METHOD=POST>', - qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$hashref->{agentnum}">!, - "Agent #", $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)"; +<FORM ACTION="<%=popurl(1)%>process/agent.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $hashref->{agentnum} %>"> +Agent #<%= $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %> -print &ntable("#cccccc", 2, ''), <<END; +<%= &ntable("#cccccc", 2, '') %> <TR> <TH ALIGN="right">Agent</TH> - <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="$hashref->{agent}"></TD> + <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<%= $hashref->{agent} %>"></TD> </TR> <TR> <TH ALIGN="right">Agent type</TH> <TD><SELECT NAME="typenum" SIZE=1> -END -foreach my $agent_type (qsearch('agent_type',{})) { - print "<OPTION VALUE=". $agent_type->typenum; - print " SELECTED" - if $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ); - print ">", $agent_type->getfield('typenum'), ": ", - $agent_type->getfield('atype'),"\n"; -} +<% foreach my $agent_type (qsearch('agent_type',{})) { %> + <OPTION VALUE="<%= $agent_type->typenum %>"<%= ( $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ) ) ? ' SELECTED' : '' %>> + <%= $agent_type->getfield('typenum') %>: <%= $agent_type->getfield('atype') %> +<% } %> -print <<END; </SELECT></TD> </TR> +<% if ( dbdef->table('agent')->column('disabled') ) { %> + <TR> + <TD ALIGN="right">Disable</TD> + <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD> + </TR> +<% } %> <TR> <TD ALIGN="right"><!--Frequency--></TD> - <TD><INPUT TYPE="hidden" NAME="freq" VALUE="$hashref->{freq}"></TD> + <TD><INPUT TYPE="hidden" NAME="freq" VALUE="<%= $hashref->{freq} %>"></TD> </TR> <TR> <TD ALIGN="right"><!--Program--></TD> - <TD><INPUT TYPE="hidden" NAME="prog" VALUE="$hashref->{prog}"></TD> + <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<%= $hashref->{prog} %>"></TD> </TR> +<% if ( dbdef->table('agent')->column('username') ) { %> + <TR> + <TD ALIGN="right">Agent interface username</TD> + <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $hashref->{username} %>"></TD> + </TR> + <TR> + <TD ALIGN="right">Agent interface password</TD> + <TD><INPUT TYPE="text" NAME="_password" VALUE="<%= $hashref->{_password} %>"></TD> + </TR> +<% } %> </TABLE> -END -print qq!<BR><INPUT TYPE="submit" VALUE="!, - $hashref->{agentnum} ? "Apply changes" : "Add agent", - qq!">!; - -print <<END; +<BR><INPUT TYPE="submit" VALUE="<%= $hashref->{agentnum} ? "Apply changes" : "Add agent" %>"> </FORM> </BODY> </HTML> -END - -%> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index eeceed889..857e769a3 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -39,6 +39,12 @@ if ( $cgi->param('error') ) { $query =~ /^(\d+)$/; $custnum=$1; $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); + if ( $cust_main->dbdef_table->column('paycvv') + && length($cust_main->paycvv) ) { + my $paycvv = $cust_main->paycvv; + $paycvv =~ s/./*/g; + $cust_main->paycvv($paycvv); + } $saved_pkgpart = 0; $username = ''; $password = ''; @@ -61,7 +67,7 @@ my $action = $custnum ? 'Edit' : 'Add'; # top my $p1 = popurl(1); -print header("Customer $action", ''); +print header("Customer $action", '', ' onUnload="myclose()"'); print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $error, "</FONT>" if $error; @@ -75,9 +81,11 @@ print qq!<FORM ACTION="${p1}process/cust_main.cgi" METHOD=POST NAME="form1">!, my $r = qq!<font color="#ff0000">*</font> !; -my @agents = qsearch( 'agent', {} ); +my %agent_search = dbdef->table('agent')->column('disabled') + ? ( 'disabled' => '' ) : (); +my @agents = qsearch( 'agent', \%agent_search ); #die "No agents created!" unless @agents; -eidiot "You have not created any agents. You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents; +eidiot "You have not created any agents (or all agents are disabled). You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents; my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first if ( scalar(@agents) == 1 ) { print qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!; @@ -183,8 +191,10 @@ END my $countrydefault = $conf->config('countrydefault') || 'US'; $cust_main->country( $countrydefault ) unless $cust_main->country; -$cust_main->state( $conf->config('statedefault') || 'CA' ) - unless $cust_main->state || $cust_main->country ne 'US'; +my $statedefault = $conf->config('statedefault') + || ($countrydefault eq 'US' ? 'CA' : ''); +$cust_main->state( $statedefault ) + unless $cust_main->state || $cust_main->country ne $countrydefault; my($county_html, $state_html, $country_html) = FS::cust_main_county::regionselector( $cust_main->county, @@ -281,8 +291,9 @@ END #false laziness with regular state $cust_main->ship_country( $countrydefault ) unless $cust_main->ship_country; - $cust_main->ship_state( $conf->config('statedefault') || 'CA' ) - unless $cust_main->ship_state || $cust_main->ship_country ne 'US'; + $cust_main->ship_state( $statedefault ) + unless $cust_main->ship_state + || $cust_main->ship_country ne $countrydefault; my($ship_county_html, $ship_state_html, $ship_country_html) = FS::cust_main_county::regionselector( $cust_main->ship_county, @@ -335,7 +346,9 @@ sub expselect { $return .= ">$_"; } $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!; - for ( 2001 .. 2037 ) { + my @t = localtime; + my $thisYear = $t[5] + 1900; + for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. 2037 ) { $return .= "<OPTION"; $return .= " SELECTED" if $_ == $y; $return .= ">$_"; @@ -398,7 +411,19 @@ if ( $payby_default eq 'HIDE' ) { print qq!<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="$invoicing_list"></TD></TR>!; print "<TR><TD>Billing type</TD></TR>", - "</TABLE>", + "</TABLE>", '<script language="JavaScript"><!-- + var mywindow = -1; + function myopen(filename,windowname,properties) { + myclose(); + mywindow = window.open(filename,windowname,properties); + } + function myclose() { + if ( mywindow != -1 ) + mywindow.close(); + mywindow = -1; + } + + //--></script>', &table("#cccccc"), "<TR>"; my($payinfo, $payname)=( @@ -416,6 +441,12 @@ if ( $payby_default eq 'HIDE' ) { 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR>${r}Exp !. expselect("COMP"), ); + if ( $cust_main->dbdef_table->column('paycvv') ) { + foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs + $payby{$payby} .= qq!<BR>CVV2 (<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>) <INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!; + } + } + my( $account, $aba ) = split('@', $payinfo); my %paybychecked = ( @@ -428,6 +459,15 @@ if ( $payby_default eq 'HIDE' ) { 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR>${r}Exp !. expselect("COMP", $cust_main->paydate), ); + if ( $cust_main->dbdef_table->column('paycvv') ) { + my $paycvv = $cust_main->paycvv; + + foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs + $paybychecked{$payby} .= qq!<BR>CVV2 (<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>) <INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!; + } + } + + $cust_main->payby($payby_default) unless $cust_main->payby; for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) { print qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!; diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi index f3d28825a..4bcfcbe9b 100755 --- a/httemplate/edit/cust_main_county.cgi +++ b/httemplate/edit/cust_main_county.cgi @@ -16,12 +16,26 @@ print qq!<FORM ACTION="!, popurl(1), <TH><FONT SIZE=-1>State</FONT></TH> <TH><FONT SIZE=-1>County</FONT></TH> <TH><FONT SIZE=-1>Taxclass</FONT><BR><FONT SIZE=-2>(per-package classification)</FONT></TH> - <TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH> +END + +if ( dbdef->table('cust_main_county')->column('taxname') ) { + print '<TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH>'; +} + +print <<END; <TH><FONT SIZE=-1>Tax</FONT></TH> <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH> - </TR> END +if ( dbdef->table('cust_main_county')->column('setuptax') ) { + print '<TH><FONT SIZE=-1>Setup<BR>fee<BR>exempt</TH>'; +} +if ( dbdef->table('cust_main_county')->column('recurtax') ) { + print '<TH><FONT SIZE=-1>Recurring<BR>fee<BR>exempt</TH>'; +} + +print '</TR>'; + foreach my $cust_main_county ( sort { $a->country cmp $b->country or $a->state cmp $b->state or $a->county cmp $b->county @@ -48,11 +62,26 @@ END , "</TD>"; print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>!; - print qq!<TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6>%</TD>!; - print qq!<TD>\$<INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum}, - qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD>!; + qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>! + if dbdef->table('cust_main_county')->column('taxname'); + + print qq!<TD><TABLE><TR><TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum}, + qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6></TD><TD>%</TD></TR></TABLE></TD>!; + print qq!<TD><TABLE><TR><TD>\$</TD><TD><INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum}, + qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD></TR></TABLE></TD>!; + + print qq!<TD><INPUT TYPE="checkbox" NAME="setuptax!. $hashref->{taxnum}. + '" VALUE="Y"'. + ( $hashref->{setuptax} =~ /^Y$/i ? ' CHECKED' : '' ). + '></TD>' + if dbdef->table('cust_main_county')->column('setuptax'); + + print qq!<TD><INPUT TYPE="checkbox" NAME="recurtax!. $hashref->{taxnum}. + '" VALUE="Y"'. + ( $hashref->{recurtax} =~ /^Y$/i ? ' CHECKED' : '' ). + '></TD>' + if dbdef->table('cust_main_county')->column('recurtax'); + print '</TR>'; } diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi index 6426eed93..48ed7916b 100755 --- a/httemplate/edit/part_bill_event.cgi +++ b/httemplate/edit/part_bill_event.cgi @@ -20,7 +20,7 @@ if ( $query && $query =~ /^(\d+)$/ ) { } else { $part_bill_event ||= new FS::part_bill_event {}; } -$action ||= $part_bill_event->pkgpart ? 'Edit' : 'Add'; +$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add'; my $hashref = $part_bill_event->hashref; print header("$action Invoice Event Definition", menubar( diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index cc60f1aeb..4d0c7391f 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -68,9 +68,10 @@ my $widget = new HTML::Widgets::SelectLayers( $html .= '</SELECT>'; } elsif ( $type eq 'textarea' ) { $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!. - qq!$value</TEXTAREA>!; + encode_entities($value). '</TEXTAREA>'; } elsif ( $type eq 'text' ) { - $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="$value" SIZE=64>!; + $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!. + encode_entities($value). '" SIZE=64>'; } elsif ( $type eq 'checkbox' ) { $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!; $html .= ' CHECKED' if $value; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index dee356250..8416b3546 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -66,12 +66,52 @@ print '<FORM NAME="dummy">'; #print qq!<INPUT TYPE="hidden" NAME="pkgpart" VALUE="$hashref->{pkgpart}">!, print "Package Part #", $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)"; -print ntable("#cccccc",2), <<END; -<TR><TD ALIGN="right">Package (customer-visible)</TD><TD><INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="$hashref->{pkg}"></TD></TR> -<TR><TD ALIGN="right">Comment (customer-hidden)</TD><TD><INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="$hashref->{comment}"></TD></TR> -<TR><TD ALIGN="right">Frequency (months) of recurring fee</TD><TD><INPUT TYPE="text" NAME="freq" VALUE="$hashref->{freq}" SIZE=3> <I>0=no recurring fee, 1=monthly, 3=quarterly, 12=yearly</TD></TR> -<TR><TD ALIGN="right">Setup fee tax exempt</TD><TD> -END +#false laziness w/view/cust_main.cgi +my %freq; +tie %freq, 'Tie::IxHash', + '0' => '(no recurring fee)', + '1d' => 'daily', + '1w' => 'weekly', + '2w' => 'biweekly (every 2 weeks)', + '1' => 'monthly', + '2' => 'bimonthly (every 2 months)', + '3' => 'quarterly (every 3 months)', + '6' => 'semiannually (every 6 months)', + '12' => 'annually', + '24' => 'biannually (every 2 years)', +; +if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) { + delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq; +} + +%> +<%= ntable("#cccccc",2) %> + <TR> + <TD ALIGN="right">Package (customer-visible)</TD> + <TD> + <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<%= $part_pkg->pkg %>"> + </TD> + </TR> + <TR> + <TD ALIGN="right">Comment (customer-hidden)</TD> + <TD> + <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%=$part_pkg->comment%>"> + </TD> + </TR> + <TR> + <TD ALIGN="right">Recurring fee frequency </TD> + <TD> + <SELECT NAME="freq"> + <% foreach my $freq ( keys %freq ) { %> + <OPTION VALUE="<%= $freq %>"<%= $freq eq $part_pkg->freq ? ' SELECTED' : '' %>><%= $freq{$freq} %> + <% } %> + </SELECT> + </TD> + </TR> + <TR> + <TD ALIGN="right">Setup fee tax exempt</TD> + <TD> +<% print '<INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y"'; print ' CHECKED' if $hashref->{setuptax} eq "Y"; @@ -112,9 +152,11 @@ print ' CHECKED' if $hashref->{disabled} eq "Y"; print '>'; print '</TD></TR></TABLE>'; -my $thead = "\n\n". ntable('#cccccc', 2). <<END; -<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH><TH BGCOLOR="#dcdcdc">Service</TH></TR> -END +my $thead = "\n\n". ntable('#cccccc', 2). + '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'; +$thead .= '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>' + if dbdef->table('pkg_svc')->column('primary_svc'); +$thead .= '<TH BGCOLOR="#dcdcdc">Service</TH></TR>'; #unless ( $cgi->param('clone') ) { #dunno why... @@ -136,9 +178,10 @@ foreach my $part_svc ( @part_svc ) { 'pkgpart' => $pkgpart, 'svcpart' => $svcpart, } ) || new FS::pkg_svc ( { - 'pkgpart' => $pkgpart, - 'svcpart' => $svcpart, - 'quantity' => 0, + 'pkgpart' => $pkgpart, + 'svcpart' => $svcpart, + 'quantity' => 0, + 'primary_svc' => '', }); #? #next unless $pkg_svc; @@ -150,7 +193,13 @@ foreach my $part_svc ( @part_svc ) { print '<TR>'; # if $count == 0 ; print qq!<TD><INPUT TYPE="text" NAME="pkg_svc$svcpart" SIZE=4 MAXLENGTH=3 VALUE="!, $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0, - qq!"></TD><TD><A HREF="part_svc.cgi?!,$part_svc->svcpart, + qq!"></TD>!; + if ( dbdef->table('pkg_svc')->column('primary_svc') ) { + print qq!<TD><INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="$svcpart"!; + print ' CHECKED' if $pkg_svc->primary_svc =~ /^Y/i; + print '></TD>'; + } + print qq!<TD><A HREF="part_svc.cgi?!,$part_svc->svcpart, qq!">!, $part_svc->getfield('svc'), "</A></TD></TR>"; # print "</TABLE></TD><TD>$thead" if ++$count == int(scalar(@part_svc) / 2); $count+=1; @@ -399,6 +448,71 @@ tie my %plans, 'Tie::IxHash', 'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); push @details, \"Last month\\\'s excess data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\", \"Last month\\\'s excess time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; \' + what.recur_flat.value + \' + $hourscharge + \' + what.recur_input_charge.value + \' * $input + \' + what.recur_output_charge.value + \' * $output + $totalcharge ;\'', }, + 'sql_generic' => { + 'name' => 'Base charge plus a metered rate from a configurable SQL query', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_flat' => { 'name' => 'Base monthly charge for this package', + 'default' => 0, + }, + 'recur_included' => { 'name' => 'Units included', + 'default' => 0, + }, + 'recur_unit_charge' => { 'name' => 'Additional charge per unit', + 'default' => 0, + }, + 'datasrc' => { 'name' => 'DBI data source', + 'default' => '', + }, + 'db_username' => { 'name' => 'Database username', + 'default' => '', + }, + 'db_password' => { 'name' => 'Database username', + 'default' => '', + }, + 'query' => { 'name' => 'SQL query', + 'default' => '', + }, + }, + 'fieldorder' => [qw( setup_fee recur_flat recur_included recur_unit_charge datasrc db_username db_password query )], + 'setup' => 'what.setup_fee.value', + # 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\") or die $DBI::errstr; \'', + 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\", \"\' + what.db_password.value + \'\" ) or die $DBI::errstr; my $sth = $dbh->prepare(\"\' + what.query.value + \'\") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq \"svc_domain\" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge.value + \';\'', + #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge + \';\'', + }, + + + + 'sql_external' => { + 'name' => 'Base charge plus additional fees for external services from a configurable SQL query', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_flat' => { 'name' => 'Base monthly charge for this package', + 'default' => 0, + }, + 'datasrc' => { 'name' => 'DBI data source', + 'default' => '', + }, + 'db_username' => { 'name' => 'Database username', + 'default' => '', + }, + 'db_password' => { 'name' => 'Database password', + 'default' => '', + }, + 'query' => { 'name' => 'SQL query', + 'default' => '', + }, + }, + 'fieldorder' => [qw( setup_fee recur_flat datasrc db_username db_password query )], + 'setup' => 'what.setup_fee.value', + 'recur' => q!'my $dbh = DBI->connect("' + what.datasrc.value + '", "' + what.db_username.value + '", "' + what.db_password.value + '" ) or die $DBI::errstr; my $sth = $dbh->prepare("' + what.query.value + '") or die $dbh->errstr; my $price = ' + what.recur_flat.value + '; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc ){ my $id = $cust_svc->svc_x->id; $sth->execute($id) or die $sth->errstr; $price += $sth->fetchrow_arrayref->[0]; } $price;'!, + + }, + ; my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } @@ -413,6 +527,10 @@ if ( $conf->exists('enable_taxclasses') ) { push @fixups, 'taxclass'; #hidden } +my @form_radio = (); +if ( dbdef->table('pkg_svc')->column('primary_svc') ) { + push @form_radio, 'pkg_svc_primary'; +} my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => $part_pkg->plan, @@ -421,7 +539,8 @@ my $widget = new HTML::Widgets::SelectLayers( 'form_action' => 'process/part_pkg.cgi', 'form_text' => [ qw(pkg comment freq clone pkgnum pkgpart), @fixups ], 'form_checkbox' => [ qw(setuptax recurtax disabled) ], - 'form_select' => [ @form_select ], + 'form_radio' => \@form_radio, + 'form_select' => \@form_select, 'fixup_callback' => sub { #my $ = @_; my $html = ''; diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 45e0b6ffe..6868ffd65 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -53,6 +53,7 @@ Services are items you offer to your customers. <LI>svc_forward - mail forwarding <LI>svc_www - Virtual domain website <LI>svc_broadband - Broadband/High-speed Internet service + <LI>svc_external - Externally-tracked service <!-- <LI>svc_charge - One-time charges (Partially unimplemented) <LI>svc_wo - Work orders (Partially unimplemented) --> @@ -129,9 +130,13 @@ my %defs = ( 'ip_addr' => 'IP address. Leave blank for automatic assignment.', 'blocknum' => 'Address block.', }, + 'svc_external' => { + #'id' => '', + #'title' => '', + }, ); - foreach $svcdb (keys(%defs)) { + foreach my $svcdb (grep dbdef->table($_), keys %defs ) { my $self = "FS::$svcdb"->new; $vfields{$svcdb} = {}; foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them @@ -151,9 +156,9 @@ my %defs = ( my @dbs = $hashref->{svcdb} ? ( $hashref->{svcdb} ) - : qw( svc_acct svc_domain svc_forward svc_www svc_broadband ); + : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_external ); - tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } @dbs; + tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; my $widget = new HTML::Widgets::SelectLayers( #'selected_layer' => $p_svcdb, 'selected_layer' => $hashref->{svcdb} || 'svc_acct', @@ -174,7 +179,7 @@ my %defs = ( $html .= '<BR><BR>'. table(). table(). "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>"; foreach my $part_export ( @part_export ) { - $html .= '<TD><INPUT TYPE="checbox"'. + $html .= '<TD><INPUT TYPE="checkbox"'. ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" '; $html .= 'CHECKED' if ( $clone || $part_svc->svcpart ) #null svcpart search causing error diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index 818382856..25c346e46 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -19,6 +19,8 @@ if ( $payby ) { $cgi->param('paydate', $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' ) ); $cgi->param('payname', $cgi->param( $payby. '_payname' ) ); + $cgi->param('paycvv', $cgi->param( $payby. '_paycvv' ) ) + if defined $cgi->param( $payby. '_paycvv' ); } $cgi->param('otaker', &getotaker ); @@ -27,6 +29,7 @@ my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); $cgi->param('invoicing_list', join(',', @invoicing_list) ); + #create new record object my $new = new FS::cust_main ( { @@ -113,6 +116,11 @@ if ( $new->custnum eq '' ) { } else { #create old record object my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } ); $error ||= "Old record not found!" unless $old; + if ( defined dbdef->table('cust_main')->column('paycvv') + && length($old->paycvv) + && $new->paycvv =~ /^\s*\*+\s*$/ ) { + $new->paycvv($old->paycvv); + } $error ||= $new->replace($old, \@invoicing_list); } diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi index 8e67140a8..5da9dea80 100755 --- a/httemplate/edit/process/cust_main_county-collapse.cgi +++ b/httemplate/edit/process/cust_main_county-collapse.cgi @@ -3,8 +3,8 @@ my($query) = $cgi->keywords; $query =~ /^(\d+)$/ or die "Illegal taxnum!"; my $taxnum = $1; -my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) - or die ("Unknown taxnum!"); +my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } ) + or die "Unknown taxnum $taxnum"; #really should do this in a .pm & start transaction diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi index 6d80ad512..9287ed150 100755 --- a/httemplate/edit/process/cust_main_county.cgi +++ b/httemplate/edit/process/cust_main_county.cgi @@ -7,11 +7,15 @@ foreach ( grep { /^tax\d+$/ } $cgi->param ) { or die "Couldn't find taxnum $taxnum!"; next unless $old->tax != $cgi->param("tax$taxnum") || $old->exempt_amount != $cgi->param("exempt_amount$taxnum") - || $old->taxname ne $cgi->param("taxname$taxnum"); + || $old->taxname ne $cgi->param("taxname$taxnum") + || $old->setuptax ne $cgi->param("setuptax$taxnum") + || $old->recurtax ne $cgi->param("recurtax$taxnum"); my %hash = $old->hash; $hash{tax} = $cgi->param("tax$taxnum"); $hash{exempt_amount} = $cgi->param("exempt_amount$taxnum"); $hash{taxname} = $cgi->param("taxname$taxnum"); + $hash{setuptax} = $cgi->param("setuptax$taxnum"); + $hash{recurtax} = $cgi->param("recurtax$taxnum"); my $new = new FS::cust_main_county \%hash; my $error = $new->replace($old); if ( $error ) { diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index d489426f9..7eada7bc8 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -62,16 +62,24 @@ if ( $error ) { foreach my $part_svc (qsearch('part_svc',{})) { my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0; + my $primary_svc = + $cgi->param('pkg_svc_primary') == $part_svc->svcpart ? 'Y' : ''; my $old_pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $pkgpart, 'svcpart' => $part_svc->svcpart, } ); my $old_quantity = $old_pkg_svc ? $old_pkg_svc->quantity : 0; - next unless $old_quantity != $quantity; #!here + my $old_primary_svc = + ( $old_pkg_svc && $old_pkg_svc->dbdef_table->column('primary_svc') ) + ? $old_pkg_svc->primary_svc + : ''; + next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc; + my $new_pkg_svc = new FS::pkg_svc( { - 'pkgpart' => $pkgpart, - 'svcpart' => $part_svc->svcpart, - 'quantity' => $quantity, + 'pkgpart' => $pkgpart, + 'svcpart' => $part_svc->svcpart, + 'quantity' => $quantity, + 'primary_svc' => $primary_svc, } ); if ( $old_pkg_svc ) { my $myerror = $new_pkg_svc->replace($old_pkg_svc); diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi index 7e40c48a7..a2fa46dd9 100644 --- a/httemplate/edit/process/router.cgi +++ b/httemplate/edit/process/router.cgi @@ -1,8 +1,5 @@ <% -use FS::UID qw(dbh); - -my $dbh = dbh; local $FS::UID::AutoCommit=0; sub check { @@ -10,7 +7,7 @@ sub check { if($error) { $cgi->param('error', $error); print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string); - $dbh->rollback; + dbh->rollback; exit; } } @@ -38,7 +35,7 @@ check($error); if ($old) { @old_psr = $old->part_svc_router; - foreach $psr (@old_psr) { + foreach my $psr (@old_psr) { if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') { # do nothing } else { @@ -64,7 +61,7 @@ foreach($cgi->param) { # Yay, everything worked! -$dbh->commit or die $dbh->errstr; +dbh->commit or die dbh->errstr; print $cgi->redirect(popurl(3). "browse/router.cgi"); %> diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi new file mode 100755 index 000000000..728cd2189 --- /dev/null +++ b/httemplate/edit/process/svc_external.cgi @@ -0,0 +1,29 @@ +<% + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum =$1; + +my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum; + +my $new = new FS::svc_external ( { + map { + ($_, scalar($cgi->param($_))); + } ( fields('svc_external'), qw( pkgnum svcpart ) ) +} ); + +my $error = ''; +if ( $svcnum ) { + $error = $new->replace($old); +} else { + $error = $new->insert; + $svcnum = $new->getfield('svcnum'); +} + +if ($error) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string ); +} else { + print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum"); +} + +%> diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi index 5c365a21e..a573c6504 100755 --- a/httemplate/edit/router.cgi +++ b/httemplate/edit/router.cgi @@ -22,6 +22,8 @@ print header("$action Router", menubar( 'View all routers' => "${p}browse/router.cgi", )); +my $p3 = popurl(3); + if($cgi->param('error')) { %> <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT> <% } %> @@ -31,6 +33,7 @@ if($cgi->param('error')) { <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%=$p3%>/browse/router.cgi"> <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%=$p3%>/edit/router.cgi"> <INPUT TYPE="hidden" NAME="routernum" VALUE="<%=$routernum%>"> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$router->svcnum%>"> Router #<%=$routernum or "(NEW)"%> <BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%=$router->routername%>"> @@ -49,12 +52,14 @@ foreach my $field ($router->virtual_fields) { </TABLE> - +<% +unless ($router->svcnum) { +%> <BR><BR>Select the service types available on this router<BR> <% -foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', - disabled => '' }) ) { + foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', + disabled => '' }) ) { %> <BR> <INPUT TYPE="checkbox" NAME="svcpart_<%=$part_svc->svcpart%>"<%= @@ -64,6 +69,8 @@ foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband', <%=$part_svc->svcpart%>: <%=$part_svc->svc%></A> <% } %> +<% } %> + <BR><BR><INPUT TYPE="submit" VALUE="Apply changes"> </FORM> </BODY></HTML> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 44606d9a0..f1b8b800b 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -281,7 +281,7 @@ if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) { } print '</TD></TR>'; -foreach $field ($svc_acct->virtual_fields) { +foreach my $field ($svc_acct->virtual_fields) { if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { # If the flag is X, it won't even show up in $svc_acct->virtual_fields. print $svc_acct->pvf($field)->widget('HTML', 'edit', diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 6828791c0..db27b3241 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -157,7 +157,7 @@ Service #<B><%=$svcnum ? $svcnum : "(NEW)"%></B><BR><BR> <% } %> <% -foreach $field ($svc_broadband->virtual_fields) { +foreach my $field ($svc_broadband->virtual_fields) { if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { print $svc_broadband->pvf($field)->widget('HTML', 'edit', $svc_broadband->getfield($field)); diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi new file mode 100644 index 000000000..bcfc85e3f --- /dev/null +++ b/httemplate/edit/svc_external.cgi @@ -0,0 +1,105 @@ +<!-- mason kludge --> +<% + +my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external ); +if ( $cgi->param('error') ) { + $svc_external = new FS::svc_external ( { + map { $_, scalar($cgi->param($_)) } fields('svc_external') + } ); + $svcnum = $svc_external->svcnum; + $pkgnum = $cgi->param('pkgnum'); + $svcpart = $cgi->param('svcpart'); + $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); + die "No part_svc entry!" unless $part_svc; +} else { + my($query) = $cgi->keywords; + if ( $query =~ /^(\d+)$/ ) { #editing + $svcnum=$1; + $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum}) + or die "Unknown (svc_external) svcnum!"; + + my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) + or die "Unknown (cust_svc) svcnum!"; + + $pkgnum=$cust_svc->pkgnum; + $svcpart=$cust_svc->svcpart; + + $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); + die "No part_svc entry!" unless $part_svc; + + } else { #adding + + foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart + $pkgnum=$1 if /^pkgnum(\d+)$/; + $svcpart=$1 if /^svcpart(\d+)$/; + } + $svc_external = new FS::svc_external { svcpart => $svcpart }; + + $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); + die "No part_svc entry!" unless $part_svc; + + $svcnum=''; + + #set fixed and default fields from part_svc + foreach my $part_svc_column ( + grep { $_->columnflag } $part_svc->all_part_svc_column + ) { + $svc_external->setfield( $part_svc_column->columnname, + $part_svc_column->columnvalue, + ); + } + + } +} +my $action = $svc_external->svcnum ? 'Edit' : 'Add'; + +my $p1 = popurl(1); +print header("External service $action", ''); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), + "</FONT>" + if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/svc_external.cgi" METHOD=POST>!; + +#display + + +#svcnum +print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>"; + +#pkgnum +print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; + +#svcpart +print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; + +my($id,$title)=( + $svc_external->id, + $svc_external->title, +); + +print &ntable("#cccccc",2), + '<TR><TD ALIGN="right">External ID</TD><TD>'. + qq!<INPUT TYPE="text" NAME="id" VALUE="$id">!. + '</TD></TR>'. + '<TR><TD ALIGN="right">Title</TD><TD>'. + qq!<INPUT TYPE="text" NAME="title" VALUE="$title">!. + '</TD></TR>'; + +foreach my $field ($svc_external->virtual_fields) { + if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { + # If the flag is X, it won't even show up in $svc_acct->virtual_fields. + print $svc_external->pvf($field)->widget('HTML', 'edit', + $svc_external->getfield($field)); + } +} + +%> + +</TABLE><BR><INPUT TYPE="submit" VALUE="Submit"> + </FORM> + </BODY> +</HTML> + diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi index 0d815b9e5..2e6c5f1a0 100755 --- a/httemplate/edit/svc_forward.cgi +++ b/httemplate/edit/svc_forward.cgi @@ -58,20 +58,17 @@ if ( $cgi->param('error') ) { my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; my %email; + +#starting with those currently attached +foreach my $method (qw( srcsvc_acct dstsvc_acct )) { + my $svc_acct = $svc_forward->$method(); + $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; +} + if ($pkgnum) { #find all possible user svcnums (and emails) - #starting with those currently attached - if ( $svc_forward->srcsvc ) { - my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $svc_forward->srcsvc } ); - $email{$svc_forward->srcsvc} = $svc_acct->email; - } - if ( $svc_forward->dstsvc ) { - my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $svc_forward->dstsvc } ); - $email{$svc_forward->dstsvc} = $svc_acct->email; - } - #and including the rest for this customer my($u_part_svc,@u_acct_svcparts); foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { @@ -98,15 +95,7 @@ if ($pkgnum) { } } -} elsif ( $action eq 'Edit' ) { - - my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_forward->srcsvc}); - $email{$svc_forward->srcsvc} = $svc_acct->email; - - $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svc_forward->dstsvc}); - $email{$svc_forward->dstsvc} = $svc_acct->email; - -} else { +} elsif ( $action eq 'Add' ) { die "\$action eq Add, but \$pkgnum is null!\n"; } @@ -115,6 +104,7 @@ my($srcsvc,$dstsvc,$dst)=( $svc_forward->dstsvc, $svc_forward->dst, ); +my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; #display @@ -130,46 +120,54 @@ my($srcsvc,$dstsvc,$dst)=( Service #<%= $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> Service: <B><%= $part_svc->svc %></B><BR><BR> -<FORM NAME="dummy"> +<FORM ACTION="process/svc_forward.cgi" METHOD="POST"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%= $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%= $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $svcpart %>"> + +<SCRIPT TYPE="text/javascript"> +function srcchanged(what) { + if ( what.options[what.selectedIndex].value == 0 ) { + what.form.src.disabled = false; + } else { + what.form.src.disabled = true; + } +} +function dstchanged(what) { + if ( what.options[what.selectedIndex].value == 0 ) { + what.form.dst.disabled = false; + } else { + what.form.dst.disabled = true; + } +} +</SCRIPT> <%= ntable("#cccccc",2) %> -<TR><TD ALIGN="right">Email to</TD><TD><SELECT NAME="srcsvc" SIZE=1> +<TR><TD ALIGN="right">Email to</TD> +<TD><SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)"> <% foreach $_ (keys %email) { %> <OPTION<%= $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION> <% } %> -</SELECT></TD></TR> - -<% - tie my %tied_email, 'Tie::IxHash', - '' => 'SELECT DESTINATION', - %email, - '0' => '(other email address)'; - my $widget = new HTML::Widgets::SelectLayers( - 'selected_layer' => $dstsvc, - 'options' => \%tied_email, - 'form_name' => 'dummy', - 'form_action' => 'process/svc_forward.cgi', - 'form_select' => ['srcsvc'], - 'html_between' => '</TD></TR></TABLE>', - 'layer_callback' => sub { - my $layer = shift; - my $html = qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!. - qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!. - qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!. - qq!<INPUT TYPE="hidden" NAME="dstsvc" VALUE="$layer">!; - if ( $layer eq '0' ) { - $html .= ntable("#cccccc",2). - '<TR><TD ALIGN="right">Destination email</TD>'. - qq!<TD><INPUT TYPE="text" NAME="dst" VALUE="$dst"></TD>!. - '</TR></TABLE>'; - } - $html .= '<BR><INPUT TYPE="submit" VALUE="Submit">'; - $html; - }, - ); -%> +<% if ( $svc_forward->dbdef_table->column('src') ) { %> + <OPTION <%= $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> +<% } %> +</SELECT> +<% if ( $svc_forward->dbdef_table->column('src') ) { %> +<INPUT TYPE="text" NAME="src" VALUE="<%= $src %>" <%= ( $src || !scalar(%email) ) ? '' : 'DISABLED' %>> +<% } %> +</TD></TR> <TR><TD ALIGN="right">Forwards to</TD> -<TD><%= $widget->html %> +<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)"> +<% foreach $_ (keys %email) { %> + <OPTION<%= $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<%= $_ %>"><%= $email{$_} %></OPTION> +<% } %> +<OPTION <%= $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION> +</SELECT> +<INPUT TYPE="text" NAME="dst" VALUE="<%= $dst %>" <%= ( $dst || !scalar(%email) ) ? '' : 'DISABLED' %>> +</TD></TR> + </TABLE> +<BR><INPUT TYPE="submit" VALUE="Submit"> +</FORM> </BODY> </HTML> diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi index 043af610b..ec5169e05 100644 --- a/httemplate/edit/svc_www.cgi +++ b/httemplate/edit/svc_www.cgi @@ -167,7 +167,7 @@ foreach $_ (keys %username) { } print "</SELECT></TD></TR>"; -foreach $field ($svc_www->virtual_fields) { +foreach my $field ($svc_www->virtual_fields) { if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { # If the flag is X, it won't even show up in $svc_acct->virtual_fields. print $svc_www->pvf($field)->widget('HTML', 'edit', diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js new file mode 100644 index 000000000..e9291e1b7 --- /dev/null +++ b/httemplate/elements/calendar-en.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, <mishoo@infoiasi.ro> +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2003\n" + // don't translate this this ;-) +"For latest version visit: http://dynarch.com/mishoo/calendar.epl\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; +Calendar._TT["MON_FIRST"] = "Display Monday first"; +Calendar._TT["SUN_FIRST"] = "Display Sunday first"; +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js new file mode 100644 index 000000000..0dc3caa00 --- /dev/null +++ b/httemplate/elements/calendar-setup.js @@ -0,0 +1,163 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + +// $Id: calendar-setup.js,v 1.3 2003-11-07 10:53:35 ivan Exp $ + +/** + * This function "patches" an input field (or other element) to use a calendar + * widget for date selection. + * + * The "params" is a single object that can have the following properties: + * + * prop. name | description + * ------------------------------------------------------------------------------------------------- + * inputField | the ID of an input field to store the date + * displayArea | the ID of a DIV or other element to show the date + * button | ID of a button or other element that will trigger the calendar + * eventName | event that will trigger the calendar, without the "on" prefix (default: "click") + * ifFormat | date format that will be stored in the input field + * daFormat | the date format that will be used to display the date in displayArea + * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) + * mondayFirst | (true/false) if true Monday is the first day of week, Sunday otherwise (default: true) + * align | alignment (default: "Bl"); if you don't know what's this see the calendar documentation + * range | array with 2 elements. Default: [1900, 2999] -- the range of years available + * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers + * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID + * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) + * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar + * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) + * onClose | function that gets called when the calendar is closed. [default] + * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. + * date | the date that the calendar will be initially displayed to + * showsTime | default: false; if true the calendar will include a time selector + * timeFormat | the time format; can be "12" or "24", default is "12" + * + * None of them is required, they all have default values. However, if you + * pass none of "inputField", "displayArea" or "button" you'll get a warning + * saying "nothing to setup". + */ +Calendar.setup = function (params) { + function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; + + param_default("inputField", null); + param_default("displayArea", null); + param_default("button", null); + param_default("eventName", "click"); + param_default("ifFormat", "%Y/%m/%d"); + param_default("daFormat", "%Y/%m/%d"); + param_default("singleClick", true); + param_default("disableFunc", null); + param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined + param_default("mondayFirst", true); + param_default("align", "Bl"); + param_default("range", [1900, 2999]); + param_default("weekNumbers", true); + param_default("flat", null); + param_default("flatCallback", null); + param_default("onSelect", null); + param_default("onClose", null); + param_default("onUpdate", null); + param_default("date", null); + param_default("showsTime", false); + param_default("timeFormat", "24"); + + var tmp = ["inputField", "displayArea", "button"]; + for (var i in tmp) { + if (typeof params[tmp[i]] == "string") { + params[tmp[i]] = document.getElementById(params[tmp[i]]); + } + } + if (!(params.flat || params.inputField || params.displayArea || params.button)) { + alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); + return false; + } + + function onSelect(cal) { + if (cal.params.flat) { + if (typeof cal.params.flatCallback == "function") { + cal.params.flatCallback(cal); + } else { + alert("No flatCallback given -- doing nothing."); + } + return false; + } + if (cal.params.inputField) { + cal.params.inputField.value = cal.date.print(cal.params.ifFormat); + } + if (cal.params.displayArea) { + cal.params.displayArea.innerHTML = cal.date.print(cal.params.daFormat); + } + if (cal.params.singleClick && cal.dateClicked) { + cal.callCloseHandler(); + } + if (typeof cal.params.onUpdate == "function") { + cal.params.onUpdate(cal); + } + }; + + if (params.flat != null) { + params.flat = document.getElementById(params.flat); + if (!params.flat) { + alert("Calendar.setup:\n Flat specified but can't find parent."); + return false; + } + var cal = new Calendar(params.mondayFirst, params.date, params.onSelect || onSelect); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.params = params; + cal.weekNumbers = params.weekNumbers; + cal.setRange(params.range[0], params.range[1]); + cal.setDateStatusHandler(params.dateStatusFunc); + cal.create(params.flat); + cal.show(); + return false; + } + + var triggerEl = params.button || params.displayArea || params.inputField; + triggerEl["on" + params.eventName] = function() { + var dateEl = params.inputField || params.displayArea; + var dateFmt = params.inputField ? params.ifFormat : params.daFormat; + var mustCreate = false; + var cal = window.calendar; + if (!window.calendar) { + window.calendar = cal = new Calendar(params.mondayFirst, + params.date, + params.onSelect || onSelect, + params.onClose || function(cal) { cal.hide(); }); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.weekNumbers = params.weekNumbers; + mustCreate = true; + } else { + cal.hide(); + } + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.parseDate(dateEl.value || dateEl.innerHTML); + cal.refresh(); + cal.showAtElement(params.displayArea || params.inputField, params.align); + return false; + }; +}; diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css new file mode 100644 index 000000000..9727d1b9a --- /dev/null +++ b/httemplate/elements/calendar-win2k-2.css @@ -0,0 +1,263 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row <TR> containing navigation buttons */ +} + +.calendar thead .daynames { /* Row <TR> containing the day names */ +} + +.calendar thead .name { /* Cells <TD> containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #f4e8f0; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #e4d8e0; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #c4b8c0; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells <TD> containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #f4e8f0; +} + +.calendar tbody .rowhilite td { + background: #e4d8e0; +} + +.calendar tbody .rowhilite td.wn { + background: #d4c8d0; +} + +.calendar tbody td.hilite { /* Hovered cells <TD> */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells <TD> */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #e4d8e0; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */ + background: #f4e8f0; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4d8e0; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #e4d8e0; + font-size: smaller; + padding: 1px; +} + +.combo .label, +.combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.combo .label-IEfix { + width: 4em; +} + +.combo .active { + background: #d4c8d0; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.combo .hilite { + background: #408; + color: #fea; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js new file mode 100644 index 000000000..3c028cc76 --- /dev/null +++ b/httemplate/elements/calendar.js @@ -0,0 +1,1641 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * ------------------------------------------------------------------ + * + * The DHTML Calendar, version 0.9.5 "Your favorite time, bis" + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + +// $Id: calendar.js,v 1.3 2003-11-07 10:53:35 ivan Exp $ + +/** The Calendar object constructor. */ +Calendar = function (mondayFirst, dateStr, onSelected, onClose) { + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.timeout = null; + this.onSelected = onSelected || null; + this.onClose = onClose || null; + this.dragging = false; + this.hidden = false; + this.minYear = 1970; + this.maxYear = 2050; + this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; + this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; + this.isPopup = true; + this.weekNumbers = true; + this.mondayFirst = mondayFirst; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + // HTML elements + this.table = null; + this.element = null; + this.tbody = null; + this.firstdayname = null; + // Combo boxes + this.monthsCombo = null; + this.yearsCombo = null; + this.hilitedMonth = null; + this.activeMonth = null; + this.hilitedYear = null; + this.activeYear = null; + // Information + this.dateClicked = false; + + // one-time initializations + if (typeof Calendar._SDN == "undefined") { + // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; + var ar = new Array(); + for (var i = 8; i > 0;) { + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); + } + Calendar._SDN = ar; + // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; + ar = new Array(); + for (var i = 12; i > 0;) { + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); + } + Calendar._SMN = ar; + } +}; + +// ** constants + +/// "static", needed for event handlers. +Calendar._C = null; + +/// detect a special case of "web browser" +Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && + !/opera/i.test(navigator.userAgent) ); + +/// detect Opera browser +Calendar.is_opera = /opera/i.test(navigator.userAgent); + +/// detect KHTML-based browsers +Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); + +// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate +// library, at some point. + +Calendar.getAbsolutePos = function(el) { + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) + SL = el.scrollLeft; + if (is_div && el.scrollTop) + ST = el.scrollTop; + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; + if (el.offsetParent) { + var tmp = Calendar.getAbsolutePos(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +Calendar.isRelated = function (el, evt) { + var related = evt.relatedTarget; + if (!related) { + var type = evt.type; + if (type == "mouseover") { + related = evt.fromElement; + } else if (type == "mouseout") { + related = evt.toElement; + } + } + while (related) { + if (related == el) { + return true; + } + related = related.parentNode; + } + return false; +}; + +Calendar.removeClass = function(el, className) { + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); +}; + +Calendar.addClass = function(el, className) { + Calendar.removeClass(el, className); + el.className += " " + className; +}; + +Calendar.getElement = function(ev) { + if (Calendar.is_ie) { + return window.event.srcElement; + } else { + return ev.currentTarget; + } +}; + +Calendar.getTargetElement = function(ev) { + if (Calendar.is_ie) { + return window.event.srcElement; + } else { + return ev.target; + } +}; + +Calendar.stopEvent = function(ev) { + ev || (ev = window.event); + if (Calendar.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } + return false; +}; + +Calendar.addEvent = function(el, evname, func) { + if (el.attachEvent) { // IE + el.attachEvent("on" + evname, func); + } else if (el.addEventListener) { // Gecko / W3C + el.addEventListener(evname, func, true); + } else { + el["on" + evname] = func; + } +}; + +Calendar.removeEvent = function(el, evname, func) { + if (el.detachEvent) { // IE + el.detachEvent("on" + evname, func); + } else if (el.removeEventListener) { // Gecko / W3C + el.removeEventListener(evname, func, true); + } else { + el["on" + evname] = null; + } +}; + +Calendar.createElement = function(type, parent) { + var el = null; + if (document.createElementNS) { + // use the XHTML namespace; IE won't normally get here unless + // _they_ "fix" the DOM2 implementation. + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } else { + el = document.createElement(type); + } + if (typeof parent != "undefined") { + parent.appendChild(el); + } + return el; +}; + +// END: UTILITY FUNCTIONS + +// BEGIN: CALENDAR STATIC FUNCTIONS + +/** Internal -- adds a set of events to make some element behave like a button. */ +Calendar._add_evs = function(el) { + with (Calendar) { + addEvent(el, "mouseover", dayMouseOver); + addEvent(el, "mousedown", dayMouseDown); + addEvent(el, "mouseout", dayMouseOut); + if (is_ie) { + addEvent(el, "dblclick", dayMouseDblClick); + el.setAttribute("unselectable", true); + } + } +}; + +Calendar.findMonth = function(el) { + if (typeof el.month != "undefined") { + return el; + } else if (typeof el.parentNode.month != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.findYear = function(el) { + if (typeof el.year != "undefined") { + return el; + } else if (typeof el.parentNode.year != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.showMonthsCombo = function () { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var mc = cal.monthsCombo; + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + if (cal.activeMonth) { + Calendar.removeClass(cal.activeMonth, "active"); + } + var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; + Calendar.addClass(mon, "active"); + cal.activeMonth = mon; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else + s.left = (cd.offsetLeft + cd.offsetWidth - mc.offsetWidth) + "px"; + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; +}; + +Calendar.showYearsCombo = function (fwd) { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var yc = cal.yearsCombo; + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + if (cal.activeYear) { + Calendar.removeClass(cal.activeYear, "active"); + } + cal.activeYear = null; + var Y = cal.date.getFullYear() + (fwd ? 1 : -1); + var yr = yc.firstChild; + var show = false; + for (var i = 12; i > 0; --i) { + if (Y >= cal.minYear && Y <= cal.maxYear) { + yr.firstChild.data = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? 2 : -2; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else + s.left = (cd.offsetLeft + cd.offsetWidth - yc.offsetWidth) + "px"; + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + } +}; + +// event handlers + +Calendar.tableMouseUp = function(ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + if (cal.timeout) { + clearTimeout(cal.timeout); + } + var el = cal.activeDiv; + if (!el) { + return false; + } + var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); + Calendar.removeClass(el, "active"); + if (target == el || target.parentNode == el) { + Calendar.cellClick(el, ev); + } + var mon = Calendar.findMonth(target); + var date = null; + if (mon) { + date = new Date(cal.date); + if (mon.month != date.getMonth()) { + date.setMonth(mon.month); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } else { + var year = Calendar.findYear(target); + if (year) { + date = new Date(cal.date); + if (year.year != date.getFullYear()) { + date.setFullYear(year.year); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } + } + with (Calendar) { + removeEvent(document, "mouseup", tableMouseUp); + removeEvent(document, "mouseover", tableMouseOver); + removeEvent(document, "mousemove", tableMouseOver); + cal._hideCombos(); + _C = null; + return stopEvent(ev); + } +}; + +Calendar.tableMouseOver = function (ev) { + var cal = Calendar._C; + if (!cal) { + return; + } + var el = cal.activeDiv; + var target = Calendar.getTargetElement(ev); + if (target == el || target.parentNode == el) { + Calendar.addClass(el, "hilite active"); + Calendar.addClass(el.parentNode, "rowhilite"); + } else { + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); + Calendar.removeClass(el, "hilite"); + Calendar.removeClass(el.parentNode, "rowhilite"); + } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (!(--i in range)) + i = range.length - 1; + } else if (!(++i in range)) + i = 0; + var newval = range[i]; + el.firstChild.data = newval; + + cal.onUpdateTime(); + } + var mon = Calendar.findMonth(target); + if (mon) { + if (mon.month != cal.date.getMonth()) { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + Calendar.addClass(mon, "hilite"); + cal.hilitedMonth = mon; + } else if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + var year = Calendar.findYear(target); + if (year) { + if (year.year != cal.date.getFullYear()) { + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + Calendar.addClass(year, "hilite"); + cal.hilitedYear = year; + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.tableMouseDown = function (ev) { + if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { + return Calendar.stopEvent(ev); + } +}; + +Calendar.calDragIt = function (ev) { + var cal = Calendar._C; + if (!(cal && cal.dragging)) { + return false; + } + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posX = ev.pageX; + posY = ev.pageY; + } + cal.hideShowCovered(); + var st = cal.element.style; + st.left = (posX - cal.xOffs) + "px"; + st.top = (posY - cal.yOffs) + "px"; + return Calendar.stopEvent(ev); +}; + +Calendar.calDragEnd = function (ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + cal.dragging = false; + with (Calendar) { + removeEvent(document, "mousemove", calDragIt); + removeEvent(document, "mouseover", stopEvent); + removeEvent(document, "mouseup", calDragEnd); + tableMouseUp(ev); + } + cal.hideShowCovered(); +}; + +Calendar.dayMouseDown = function(ev) { + var el = Calendar.getElement(ev); + if (el.disabled) { + return false; + } + var cal = el.calendar; + cal.activeDiv = el; + Calendar._C = cal; + if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) + el._current = el.firstChild.data; + addClass(el, "hilite active"); + addEvent(document, "mouseover", tableMouseOver); + addEvent(document, "mousemove", tableMouseOver); + addEvent(document, "mouseup", tableMouseUp); + } else if (cal.isPopup) { + cal._dragStart(ev); + } + if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); + } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); + } else { + cal.timeout = null; + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseDblClick = function(ev) { + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); + if (Calendar.is_ie) { + document.selection.empty(); + } +}; + +Calendar.dayMouseOver = function(ev) { + var el = Calendar.getElement(ev); + if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { + return false; + } + if (el.ttip) { + if (el.ttip.substr(0, 1) == "_") { + var date = null; + with (el.calendar.date) { + date = new Date(getFullYear(), getMonth(), el.caldate); + } + el.ttip = date.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.firstChild.data = el.ttip; + } + if (el.navtype != 300) { + Calendar.addClass(el, "hilite"); + if (el.caldate) { + Calendar.addClass(el.parentNode, "rowhilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseOut = function(ev) { + with (Calendar) { + var el = getElement(ev); + if (isRelated(el, ev) || _C || el.disabled) { + return false; + } + removeClass(el, "hilite"); + if (el.caldate) { + removeClass(el.parentNode, "rowhilite"); + } + el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"]; + return stopEvent(ev); + } +}; + +/** + * A generic "click" handler :) handles all types of buttons defined in this + * calendar. + */ +Calendar.cellClick = function(el, ev) { + var cal = el.calendar; + var closing = false; + var newdate = false; + var date = null; + if (typeof el.navtype == "undefined") { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + cal.date.setDate(el.caldate); + date = cal.date; + newdate = true; + // a date was clicked + cal.dateClicked = true; + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = (el.navtype == 0) ? new Date() : new Date(cal.date); + // unless "today" was clicked, we assume no date was clicked so + // the selected handler will know not to close the calenar when + // in single-click mode. + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; + var year = date.getFullYear(); + var mon = date.getMonth(); + function setMonth(m) { + var day = date.getDate(); + var max = date.getMonthDays(m); + if (day > max) { + date.setDate(max); + } + date.setMonth(m); + }; + switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; + case -2: + if (year > cal.minYear) { + date.setFullYear(year - 1); + } + break; + case -1: + if (mon > 0) { + setMonth(mon - 1); + } else if (year-- > cal.minYear) { + date.setFullYear(year); + setMonth(11); + } + break; + case 1: + if (mon < 11) { + setMonth(mon + 1); + } else if (year < cal.maxYear) { + date.setFullYear(year + 1); + setMonth(0); + } + break; + case 2: + if (year < cal.maxYear) { + date.setFullYear(year + 1); + } + break; + case 100: + cal.setMondayFirst(!cal.mondayFirst); + return; + case 50: + var range = el._range; + var current = el.firstChild.data; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (!(--i in range)) + i = range.length - 1; + } else if (!(++i in range)) + i = 0; + var newval = range[i]; + el.firstChild.data = newval; + cal.onUpdateTime(); + return; + case 0: + // TODAY will bring us here + if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { + // remember, "date" was previously set to new + // Date() if TODAY was clicked; thus, it + // contains today date. + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } + } + if (newdate) { + cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + } +}; + +// END: CALENDAR STATIC FUNCTIONS + +// BEGIN: CALENDAR OBJECT FUNCTIONS + +/** + * This function creates the calendar inside the given parent. If _par is + * null than it creates a popup calendar inside the BODY element. If _par is + * an element, be it BODY, then it creates a non-popup calendar (still + * hidden). Some properties need to be set before calling this function. + */ +Calendar.prototype.create = function (_par) { + var parent = null; + if (! _par) { + // default parent is the document body, in which case we create + // a popup calendar. + parent = document.getElementsByTagName("body")[0]; + this.isPopup = true; + } else { + parent = _par; + this.isPopup = false; + } + this.date = this.dateStr ? new Date(this.dateStr) : new Date(); + + var table = Calendar.createElement("table"); + this.table = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.calendar = this; + Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); + + var div = Calendar.createElement("div"); + this.element = div; + div.className = "calendar"; + if (this.isPopup) { + div.style.position = "absolute"; + div.style.display = "none"; + } + div.appendChild(table); + + var thead = Calendar.createElement("thead", table); + var cell = null; + var row = null; + + var cal = this; + var hh = function (text, cs, navtype) { + cell = Calendar.createElement("td", row); + cell.colSpan = cs; + cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; + Calendar._add_evs(cell); + cell.calendar = cal; + cell.navtype = navtype; + if (text.substr(0, 1) != "&") { + cell.appendChild(document.createTextNode(text)); + } + else { + // FIXME: dirty hack for entities + cell.innerHTML = text; + } + return cell; + }; + + row = Calendar.createElement("tr", thead); + var title_length = 6; + (this.isPopup) && --title_length; + (this.weekNumbers) && ++title_length; + + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; + this.title = hh("", title_length, 300); + this.title.className = "title"; + if (this.isPopup) { + this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; + this.title.style.cursor = "move"; + hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; + } + + row = Calendar.createElement("tr", thead); + row.className = "headrow"; + + this._nav_py = hh("«", 1, -2); + this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; + + this._nav_pm = hh("‹", 1, -1); + this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; + + this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); + this._nav_now.ttip = Calendar._TT["GO_TODAY"]; + + this._nav_nm = hh("›", 1, 1); + this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; + + this._nav_ny = hh("»", 1, 2); + this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; + + // day names + row = Calendar.createElement("tr", thead); + row.className = "daynames"; + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.className = "name wn"; + cell.appendChild(document.createTextNode(Calendar._TT["WK"])); + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + cell.appendChild(document.createTextNode("")); + if (!i) { + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + } + this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; + this._displayWeekdays(); + + var tbody = Calendar.createElement("tbody", table); + this.tbody = tbody; + + for (i = 6; i > 0; --i) { + row = Calendar.createElement("tr", tbody); + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.appendChild(document.createTextNode("")); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + cell.appendChild(document.createTextNode("")); + cell.calendar = this; + Calendar._add_evs(cell); + } + } + + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.appendChild(document.createTextNode(init)); + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.appendChild(document.createTextNode(":")); + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var hrs = this.date.getHours(); + var mins = this.date.getMinutes(); + var pm = (hrs > 12); + if (pm && t12) hrs -= 12; + H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs; + M.firstChild.data = (mins < 10) ? ("0" + mins) : mins; + if (t12) + AP.firstChild.data = pm ? "pm" : "am"; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.firstChild.data, 10); + if (t12) { + if (/pm/i.test(AP.firstChild.data) && h < 12) + h += 12; + else if (/am/i.test(AP.firstChild.data) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.firstChild.data, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + + var tfoot = Calendar.createElement("tfoot", table); + + row = Calendar.createElement("tr", tfoot); + row.className = "footrow"; + + cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); + cell.className = "ttip"; + if (this.isPopup) { + cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; + cell.style.cursor = "move"; + } + this.tooltips = cell; + + div = Calendar.createElement("div", this.element); + this.monthsCombo = div; + div.className = "combo"; + for (i = 0; i < Calendar._MN.length; ++i) { + var mn = Calendar.createElement("div"); + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; + mn.month = i; + mn.appendChild(document.createTextNode(Calendar._SMN[i])); + div.appendChild(mn); + } + + div = Calendar.createElement("div", this.element); + this.yearsCombo = div; + div.className = "combo"; + for (i = 12; i > 0; --i) { + var yr = Calendar.createElement("div"); + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; + yr.appendChild(document.createTextNode("")); + div.appendChild(yr); + } + + this._init(this.mondayFirst, this.date); + parent.appendChild(this.element); +}; + +/** keyboard navigation, only for popup calendars */ +Calendar._keyEvent = function(ev) { + if (!window.calendar) { + return false; + } + (Calendar.is_ie) && (ev = window.event); + var cal = window.calendar; + var act = (Calendar.is_ie || ev.type == "keypress"); + if (ev.ctrlKey) { + switch (ev.keyCode) { + case 37: // KEY left + act && Calendar.cellClick(cal._nav_pm); + break; + case 38: // KEY up + act && Calendar.cellClick(cal._nav_py); + break; + case 39: // KEY right + act && Calendar.cellClick(cal._nav_nm); + break; + case 40: // KEY down + act && Calendar.cellClick(cal._nav_ny); + break; + default: + return false; + } + } else switch (ev.keyCode) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.hide(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var date = cal.date.getDate() - 1; + var el = cal.currentDateEl; + var ne = null; + var prev = (ev.keyCode == 37) || (ev.keyCode == 38); + switch (ev.keyCode) { + case 37: // KEY left + (--date >= 0) && (ne = cal.ar_days[date]); + break; + case 38: // KEY up + date -= 7; + (date >= 0) && (ne = cal.ar_days[date]); + break; + case 39: // KEY right + (++date < cal.ar_days.length) && (ne = cal.ar_days[date]); + break; + case 40: // KEY down + date += 7; + (date < cal.ar_days.length) && (ne = cal.ar_days[date]); + break; + } + if (!ne) { + if (prev) { + Calendar.cellClick(cal._nav_pm); + } else { + Calendar.cellClick(cal._nav_nm); + } + date = (prev) ? cal.date.getMonthDays() : 1; + el = cal.currentDateEl; + ne = cal.ar_days[date - 1]; + } + Calendar.removeClass(el, "selected"); + Calendar.addClass(ne, "selected"); + cal.date.setDate(ne.caldate); + cal.callHandler(); + cal.currentDateEl = ne; + } + break; + case 13: // KEY enter + if (act) { + cal.callHandler(); + cal.hide(); + } + break; + default: + return false; + } + return Calendar.stopEvent(ev); +}; + +/** + * (RE)Initializes the calendar to the given date and style (if mondayFirst is + * true it makes Monday the first day of week, otherwise the weeks start on + * Sunday. + */ +Calendar.prototype._init = function (mondayFirst, date) { + var today = new Date(); + var year = date.getFullYear(); + if (year < this.minYear) { + year = this.minYear; + date.setFullYear(year); + } else if (year > this.maxYear) { + year = this.maxYear; + date.setFullYear(year); + } + this.mondayFirst = mondayFirst; + this.date = new Date(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + date.setDate(1); + var wday = date.getDay(); + var MON = mondayFirst ? 1 : 0; + var SAT = mondayFirst ? 5 : 6; + var SUN = mondayFirst ? 6 : 0; + if (mondayFirst) { + wday = (wday > 0) ? (wday - 1) : 6; + } + var iday = 1; + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var hasToday = ((today.getFullYear() == year) && (today.getMonth() == month)); + var todayDate = today.getDate(); + var week_number = date.getWeekNumber(); + var ar_days = new Array(); + for (var i = 0; i < 6; ++i) { + if (iday > no_days) { + row.className = "emptyrow"; + row = row.nextSibling; + continue; + } + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.firstChild.data = week_number; + cell = cell.nextSibling; + } + ++week_number; + row.className = "daysrow"; + for (var j = 0; j < 7; ++j) { + cell.className = "day"; + if ((!i && j < wday) || iday > no_days) { + // cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + cell = cell.nextSibling; + continue; + } + cell.disabled = false; + cell.firstChild.data = iday; + if (typeof this.getDateStatus == "function") { + date.setDate(iday); + var status = this.getDateStatus(date, year, month, iday); + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + ar_days[ar_days.length] = cell; + cell.caldate = iday; + cell.ttip = "_"; + if (iday == mday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (hasToday && (iday == todayDate)) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (wday == SAT || wday == SUN) { + cell.className += " weekend"; + } + } + ++iday; + ((++wday) ^ 7) || (wday = 0); + cell = cell.nextSibling; + } + row = row.nextSibling; + } + this.ar_days = ar_days; + this.title.firstChild.data = Calendar._MN[month] + ", " + year; + this.onSetTime(); + // PROFILE + // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms"; +}; + +/** + * Calls _init function above for going to a certain date (but only if the + * date is different than the currently selected one). + */ +Calendar.prototype.setDate = function (date) { + if (!date.equalsTo(this.date)) { + this._init(this.mondayFirst, date); + } +}; + +/** + * Refreshes the calendar. Useful if the "disabledHandler" function is + * dynamic, meaning that the list of disabled date can change at runtime. + * Just * call this function if you think that the list of disabled dates + * should * change. + */ +Calendar.prototype.refresh = function () { + this._init(this.mondayFirst, this.date); +}; + +/** Modifies the "mondayFirst" parameter (EU/US style). */ +Calendar.prototype.setMondayFirst = function (mondayFirst) { + this._init(mondayFirst, this.date); + this._displayWeekdays(); +}; + +/** + * Allows customization of what dates are enabled. The "unaryFunction" + * parameter must be a function object that receives the date (as a JS Date + * object) and returns a boolean value. If the returned value is true then + * the passed date will be marked as disabled. + */ +Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { + this.getDateStatus = unaryFunction; +}; + +/** Customization of allowed year range for the calendar. */ +Calendar.prototype.setRange = function (a, z) { + this.minYear = a; + this.maxYear = z; +}; + +/** Calls the first user handler (selectedHandler). */ +Calendar.prototype.callHandler = function () { + if (this.onSelected) { + this.onSelected(this, this.date.print(this.dateFormat)); + } +}; + +/** Calls the second user handler (closeHandler). */ +Calendar.prototype.callCloseHandler = function () { + if (this.onClose) { + this.onClose(this); + } + this.hideShowCovered(); +}; + +/** Removes the calendar object from the DOM tree and destroys it. */ +Calendar.prototype.destroy = function () { + var el = this.element.parentNode; + el.removeChild(this.element); + Calendar._C = null; + window.calendar = null; +}; + +/** + * Moves the calendar element to a different section in the DOM tree (changes + * its parent). + */ +Calendar.prototype.reparent = function (new_parent) { + var el = this.element; + el.parentNode.removeChild(el); + new_parent.appendChild(el); +}; + +// This gets called when the user presses a mouse button anywhere in the +// document, if the calendar is shown. If the click was outside the open +// calendar this function closes it. +Calendar._checkCalendar = function(ev) { + if (!window.calendar) { + return false; + } + var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); + for (; el != null && el != calendar.element; el = el.parentNode); + if (el == null) { + // calls closeHandler which should hide the calendar. + window.calendar.callCloseHandler(); + return Calendar.stopEvent(ev); + } +}; + +/** Shows the calendar. */ +Calendar.prototype.show = function () { + var rows = this.table.getElementsByTagName("tr"); + for (var i = rows.length; i > 0;) { + var row = rows[--i]; + Calendar.removeClass(row, "rowhilite"); + var cells = row.getElementsByTagName("td"); + for (var j = cells.length; j > 0;) { + var cell = cells[--j]; + Calendar.removeClass(cell, "hilite"); + Calendar.removeClass(cell, "active"); + } + } + this.element.style.display = "block"; + this.hidden = false; + if (this.isPopup) { + window.calendar = this; + Calendar.addEvent(document, "keydown", Calendar._keyEvent); + Calendar.addEvent(document, "keypress", Calendar._keyEvent); + Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); + } + this.hideShowCovered(); +}; + +/** + * Hides the calendar. Also removes any "hilite" from the class of any TD + * element. + */ +Calendar.prototype.hide = function () { + if (this.isPopup) { + Calendar.removeEvent(document, "keydown", Calendar._keyEvent); + Calendar.removeEvent(document, "keypress", Calendar._keyEvent); + Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); + } + this.element.style.display = "none"; + this.hidden = true; + this.hideShowCovered(); +}; + +/** + * Shows the calendar at a given absolute position (beware that, depending on + * the calendar element style -- position property -- this might be relative + * to the parent's containing rectangle). + */ +Calendar.prototype.showAt = function (x, y) { + var s = this.element.style; + s.left = x + "px"; + s.top = y + "px"; + this.show(); +}; + +/** Shows the calendar near a given element. */ +Calendar.prototype.showAtElement = function (el, opts) { + var self = this; + var p = Calendar.getAbsolutePos(el); + if (!opts || typeof opts != "string") { + this.showAt(p.x, p.y + el.offsetHeight); + return true; + } + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "r": p.x += el.offsetWidth - w; break; + case "l": break; // already there + } + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); +}; + +/** Customizes the date format. */ +Calendar.prototype.setDateFormat = function (str) { + this.dateFormat = str; +}; + +/** Customizes the tooltip date format. */ +Calendar.prototype.setTtDateFormat = function (str) { + this.ttDateFormat = str; +}; + +/** + * Tries to identify the date represented in a string. If successful it also + * calls this.setDate which moves the calendar to the given date. + */ +Calendar.prototype.parseDate = function (str, fmt) { + var y = 0; + var m = -1; + var d = 0; + var a = str.split(/\W+/); + if (!fmt) { + fmt = this.dateFormat; + } + var b = []; + fmt.replace(/(%.)/g, function(str, par) { + return b[b.length] = par; + }); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (b[i] == "%a" || b[i] == "%A") { + continue; + } + if (b[i] == "%d" || b[i] == "%e") { + d = parseInt(a[i], 10); + } + if (b[i] == "%m") { + m = parseInt(a[i], 10) - 1; + } + if (b[i] == "%Y" || b[i] == "%y") { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } + if (b[i] == "%b" || b[i] == "%B") { + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + } else if (/%[HIkl]/.test(b[i])) { + hr = parseInt(a[i], 10); + } else if (/%[pP]/.test(b[i])) { + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + } else if (b[i] == "%M") { + min = parseInt(a[i], 10); + } + } + if (y != 0 && m != -1 && d != 0) { + this.setDate(new Date(y, m, d, hr, min, 0)); + return; + } + y = 0; m = -1; d = 0; + for (i = 0; i < a.length; ++i) { + if (a[i].search(/[a-zA-Z]+/) != -1) { + var t = -1; + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } + } + if (t != -1) { + if (m != -1) { + d = m+1; + } + m = t; + } + } else if (parseInt(a[i], 10) <= 12 && m == -1) { + m = a[i]-1; + } else if (parseInt(a[i], 10) > 31 && y == 0) { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } else if (d == 0) { + d = a[i]; + } + } + if (y == 0) { + var today = new Date(); + y = today.getFullYear(); + } + if (m != -1 && d != 0) { + this.setDate(new Date(y, m, d, hr, min, 0)); + } +}; + +Calendar.prototype.hideShowCovered = function () { + var self = this; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = self.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var MON = this.mondayFirst ? 0 : 1; + var SUN = this.mondayFirst ? 6 : 0; + var SAT = this.mondayFirst ? 5 : 6; + var cell = this.firstdayname; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + if (!i) { + cell.ttip = this.mondayFirst ? Calendar._TT["SUN_FIRST"] : Calendar._TT["MON_FIRST"]; + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + if (i == SUN || i == SAT) { + Calendar.addClass(cell, "weekend"); + } + cell.firstChild.data = Calendar._SDN[i + 1 - MON]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseover", stopEvent); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +/** Returns the number of days in the current month */ +Date.prototype.getMonthDays = function(month) { + var year = this.getFullYear(); + if (typeof month == "undefined") { + month = this.getMonth(); + } + if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { + return 29; + } else { + return Date._MD[month]; + } +}; + +/** Returns the number of day in the year. */ +Date.prototype.getDayOfYear = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); +}; + +/** Returns the number of the week in year, as defined in ISO 8601. */ +Date.prototype.getWeekNumber = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0); + var time = now - then; + var day = then.getDay(); // 0 means Sunday + if (day == 0) day = 7; + (day > 4) && (day -= 4) || (day += 3); + return Math.round(((time / Date.DAY) + day) / 7); +}; + +/** Checks dates equality (ignores time) */ +Date.prototype.equalsTo = function(date) { + return ((this.getFullYear() == date.getFullYear()) && + (this.getMonth() == date.getMonth()) && + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); +}; + +/** Prints the date in a string according to the given format. */ +Date.prototype.print = function (str) { + var m = this.getMonth(); + var d = this.getDate(); + var y = this.getFullYear(); + var wn = this.getWeekNumber(); + var w = this.getDay(); + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? "PM" : "AM"; + s["%P"] = pm ? "pm" : "am"; + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + var re = Date._msh_formatRegexp; + if (typeof re == "undefined") { + var tmp = ""; + for (var i in s) + tmp += tmp ? ("|" + i) : i; + Date._msh_formatRegexp = re = new RegExp("(" + tmp + ")", 'g'); + } + return str.replace(re, function(match, par) { return s[par]; }); +}; + +// END: DATE OBJECT PATCHES + +// global object that remembers the calendar +window.calendar = null; diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js new file mode 100644 index 000000000..029496a74 --- /dev/null +++ b/httemplate/elements/calendar_stripped.js @@ -0,0 +1,12 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * ------------------------------------------------------------------ + * + * The DHTML Calendar, version 0.9.5 "Your favorite time, bis" + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + Calendar=function(mondayFirst,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.mondayFirst=mondayFirst;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=Calendar.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-mc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?2:-2;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-yc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseover",stopEvent);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50)el._current=el.firstChild.data;addClass(el,"hilite active");addEvent(document,"mouseover",tableMouseOver);addEvent(document,"mousemove",tableMouseOver);addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){var date=null;with(el.calendar.date){date=new Date(getFullYear(),getMonth(),el.caldate);}el.ttip=date.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date.setDate(el.caldate);date=cal.date;newdate=true;cal.dateClicked=true;}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setMondayFirst(!cal.mondayFirst);return;case 50:var range=el._range;var current=el.firstChild.data;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}}if(newdate){cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;if(text.substr(0,1)!="&"){cell.appendChild(document.createTextNode(text));}else{cell.innerHTML=text;}return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.appendChild(document.createTextNode(Calendar._TT["WK"]));}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.appendChild(document.createTextNode(init));part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.appendChild(document.createTextNode(":"));span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var hrs=this.date.getHours();var mins=this.date.getMinutes();var pm=(hrs>12);if(pm&&t12)hrs-=12;H.firstChild.data=(hrs<10)?("0"+hrs):hrs;M.firstChild.data=(mins<10)?("0"+mins):mins;if(t12)AP.firstChild.data=pm?"pm":"am";};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.firstChild.data,10);if(t12){if(/pm/i.test(AP.firstChild.data)&&h<12)h+=12;else if(/am/i.test(AP.firstChild.data)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.firstChild.data,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.appendChild(document.createTextNode(Calendar._SMN[i]));div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.mondayFirst,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.hide();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;case 40:date+=7;(date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;}if(!ne){if(prev){Calendar.cellClick(cal._nav_pm);}else{Calendar.cellClick(cal._nav_nm);}date=(prev)?cal.date.getMonthDays():1;el=cal.currentDateEl;ne=cal.ar_days[date-1];}Calendar.removeClass(el,"selected");Calendar.addClass(ne,"selected");cal.date.setDate(ne.caldate);cal.callHandler();cal.currentDateEl=ne;}break;case 13:if(act){cal.callHandler();cal.hide();}break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(mondayFirst,date){var today=new Date();var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.mondayFirst=mondayFirst;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var wday=date.getDay();var MON=mondayFirst?1:0;var SAT=mondayFirst?5:6;var SUN=mondayFirst?6:0;if(mondayFirst){wday=(wday>0)?(wday-1):6;}var iday=1;var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var hasToday=((today.getFullYear()==year)&&(today.getMonth()==month));var todayDate=today.getDate();var week_number=date.getWeekNumber();var ar_days=new Array();for(var i=0;i<6;++i){if(iday>no_days){row.className="emptyrow";row=row.nextSibling;continue;}var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=week_number;cell=cell.nextSibling;}++week_number;row.className="daysrow";for(var j=0;j<7;++j){cell.className="day";if((!i&&j<wday)||iday>no_days){cell.innerHTML=" ";cell.disabled=true;cell=cell.nextSibling;continue;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){date.setDate(iday);var status=this.getDateStatus(date,year,month,iday);if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=iday;cell.ttip="_";if(iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(hasToday&&(iday==todayDate)){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(wday==SAT||wday==SUN){cell.className+=" weekend";}}++iday;((++wday)^ 7)||(wday=0);cell=cell.nextSibling;}row=row.nextSibling;}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.mondayFirst,date);}};Calendar.prototype.refresh=function(){this._init(this.mondayFirst,this.date);};Calendar.prototype.setMondayFirst=function(mondayFirst){this._init(mondayFirst,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window.calendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=[];fmt.replace(/(%.)/g,function(str,par){return b[b.length]=par;});var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(b[i]=="%a"||b[i]=="%A"){continue;}if(b[i]=="%d"||b[i]=="%e"){d=parseInt(a[i],10);}if(b[i]=="%m"){m=parseInt(a[i],10)-1;}if(b[i]=="%Y"||b[i]=="%y"){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}if(b[i]=="%b"||b[i]=="%B"){for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}}else if(/%[HIkl]/.test(b[i])){hr=parseInt(a[i],10);}else if(/%[pP]/.test(b[i])){if(/pm/i.test(a[i])&&hr<12)hr+=12;}else if(b[i]=="%M"){min=parseInt(a[i],10);}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));return;}y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));}};Calendar.prototype.hideShowCovered=function(){var self=this;Calendar.continuation_for_the_fucking_khtml_browser=function(){function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=self.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(self.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype._displayWeekdays=function(){var MON=this.mondayFirst?0:1;var SUN=this.mondayFirst?6:0;var SAT=this.mondayFirst?5:6;var cell=this.firstdayname;for(var i=0;i<7;++i){cell.className="day name";if(!i){cell.ttip=this.mondayFirst?Calendar._TT["SUN_FIRST"]:Calendar._TT["MON_FIRST"];cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}if(i==SUN||i==SAT){Calendar.addClass(cell,"weekend");}cell.firstChild.data=Calendar._SDN[i+1-MON];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseover",stopEvent);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,1,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,1,0,0,0);var time=now-then;var day=then.getDay();if(day==0)day=7;(day>4)&&(day-=4)||(day+=3);return Math.round(((time/Date.DAY)+day)/7);};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=Date._msh_formatRegexp;if(typeof re=="undefined"){var tmp="";for(var i in s)tmp+=tmp?("|"+i):i;Date._msh_formatRegexp=re=new RegExp("("+tmp+")",'g');}return str.replace(re,function(match,par){return s[par];});};window.calendar=null;
\ No newline at end of file diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html new file mode 100644 index 000000000..581bbabc7 --- /dev/null +++ b/httemplate/elements/header.html @@ -0,0 +1,19 @@ +<% + my($title, $menubar) = @_; + my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. +%> + <HTML> + <HEAD> + <TITLE> + <%= $title %> + </TITLE> + <META HTTP-Equiv="Cache-Control" Content="no-cache"> + <META HTTP-Equiv="Pragma" Content="no-cache"> + <META HTTP-Equiv="Expires" Content="0"> + </HEAD> + <BODY BGCOLOR="#e8e8e8"<%= $etc %>> + <FONT SIZE=7> + <%= $title %> + </FONT> + <BR><BR> + <%= $menubar ? "$menubar<BR><BR>" : '' %> diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html new file mode 100644 index 000000000..87a50312c --- /dev/null +++ b/httemplate/elements/menubar.html @@ -0,0 +1,8 @@ +<% + my($item, $url, @html); + while (@_) { + ($item, $url) = splice(@_,0,2); + push @html, qq!<A HREF="$url">$item</A>!; + } +%> +<%= join(' | ', @html) %> diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html new file mode 100644 index 000000000..db9ff83bf --- /dev/null +++ b/httemplate/elements/pager.html @@ -0,0 +1,42 @@ +<% + + my %opt = @_; + + my $pager = ''; + if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) { + unless ( $opt{'offset'} == 0 ) { + $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'}); +%> + + <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A> + +<% + } + my $page = 0; + for ( my $poff = 0; $poff < $opt{'total'}; $poff += $opt{'maxrecords'} ) { + $page++; + if ( $opt{'offset'} == $poff ) { +%> + + <FONT SIZE="+2"><%= $page %></FONT> + +<% + } else { + $cgi->param('offset', $poff); +%> + + <A HREF="<%= $cgi->self_url %>">$page</A> + +<% + } + } + unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) { + $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'}); +%> + + <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A> + +<% + } + } +%> diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html new file mode 100644 index 000000000..3b6108719 --- /dev/null +++ b/httemplate/elements/table.html @@ -0,0 +1,8 @@ +<% + my $color = shift; + if ( $color ) { +%> + <TABLE BGCOLOR="<%= $color %>" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> +<% } else { %> + <TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999"> +<% } %> diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi index 944019a7a..76f1bd7bd 100755 --- a/httemplate/graph/money_time-graph.cgi +++ b/httemplate/graph/money_time-graph.cgi @@ -62,14 +62,14 @@ while ( $syear < $eyear || ( $syear == $eyear && $smonth < $emonth ) ) { my $refunded = $refunded_sth->fetchrow_arrayref->[0] || 0; #horrible local kludge that doesn't even really work right - my $expenses_sql = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND 0 < ( select count(*) from cust_bill_pkg, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%' )"; + my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND 0 < ( select count(*) from cust_bill_pkg, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%' )"; -# my $expenses_sql = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'"; - my $expenses_sth = dbh->prepare($expenses_sql) or die dbh->errstr; - $expenses_sth->execute or die $expenses_sth->errstr; - my $expenses = $expenses_sth->fetchrow_arrayref->[0] || 0; +# my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'"; + my $expenses_sth2 = dbh->prepare($expenses_sql2) or die dbh->errstr; + $expenses_sth2->execute or die $expenses_sth2->errstr; + my $expenses2 = $expenses_sth2->fetchrow_arrayref->[0] || 0; - push @{$data{cash}}, $paid-$refunded-$expenses; + push @{$data{cash}}, $paid-$refunded-$expenses2; } @@ -101,7 +101,7 @@ my @data = ( \@labels, #$chart->cgi_png(\@data); http_header('Content-Type' => 'image/png' ); -$Response->{ContentType} = 'image/png'; +#$Response->{ContentType} = 'image/png'; $chart->_set_colors(); diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index e24157ccb..de8f6ee0c 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -1,4 +1,4 @@ -<!-- mason kludge %> +<!-- mason kludge --> <% #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); @@ -32,9 +32,9 @@ my $emonth = $cgi->param('emonth') || $curmon+1; Cashflow (payments - refunds)<BR> <BR> From <SELECT NAME="smonth"> -<% my @m = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); - foreach my $m ( 1..12 ) { %> -<OPTION VALUE="<%= $m %>"<%= $m == $smonth ? ' SELECTED' : '' %>><%= $m[$m-1] %> +<% my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); %> +<% foreach my $mon ( 1..12 ) { %> +<OPTION VALUE="<%= $mon %>"<%= $mon == $smonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %> <% } %> </SELECT> <SELECT NAME="syear"> @@ -43,8 +43,8 @@ From <SELECT NAME="smonth"> <% } %> </SELECT> to <SELECT NAME="emonth"> -<% foreach my $m ( 1..12 ) { %> -<OPTION VALUE="<%= $m %>"<%= $m == $emonth ? ' SELECTED' : '' %>><%= $m[$m-1] %> +<% foreach my $mon ( 1..12 ) { %> +<OPTION VALUE="<%= $mon %>"<%= $mon == $emonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %> <% } %> </SELECT> <SELECT NAME="eyear"> diff --git a/httemplate/images/calendar.png b/httemplate/images/calendar.png Binary files differnew file mode 100644 index 000000000..163266174 --- /dev/null +++ b/httemplate/images/calendar.png diff --git a/httemplate/images/cvv2.png b/httemplate/images/cvv2.png Binary files differnew file mode 100644 index 000000000..4610dcbe6 --- /dev/null +++ b/httemplate/images/cvv2.png diff --git a/httemplate/images/cvv2_amex.png b/httemplate/images/cvv2_amex.png Binary files differnew file mode 100644 index 000000000..21c36a0ab --- /dev/null +++ b/httemplate/images/cvv2_amex.png diff --git a/httemplate/index.html b/httemplate/index.html index 1895f2912..d39f8b04d 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -11,8 +11,8 @@ </td><td> <font color="#ff0000" size=7>freeside main menu</font> </td><td align=right valign=bottom> - version 1.5.0pre2 - <BR><A HREF="http://www.sisd.com/freeside">Freeside home page</A> + version %%%VERSION%%% + <BR><A HREF="http://www.sisd.com/freeside">Freeside home page</A> <BR><A HREF="docs/">Documentation</A> </td></tr> </table> @@ -34,7 +34,8 @@ <FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="phone_on" VALUE="1">Phone # <INPUT TYPE="text" NAME="phone_text"><INPUT TYPE="submit" VALUE="Search"></FORM> <BR><FORM ACTION="search/svc_acct.cgi" METHOD="POST">Username <INPUT TYPE="text" NAME="username"><SELECT NAME="username_type"><OPTION VALUE="All">(all)</OPTION><OPTION>Fuzzy</OPTION><OPTION>Substring</OPTION><OPTION SELECTED>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_acct.cgi?username">all accounts by username</A> or <A HREF="search/svc_acct.cgi?uid">uid</A></FORM> <BR><FORM ACTION="search/svc_domain.cgi" METHOD="POST">Domain <INPUT TYPE="text" NAME="domain"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_domain.cgi?domain">all domains</A></FORM> -<!-- <LI><A HREF="search/svc_forward.html">mail forwards (by ?)</A>--> + <BR><A HREF="search/svc_forward.cgi?svcnum">all mail forwards by svcnum</A><BR> + <BR> </TD></TR> </TABLE> @@ -56,7 +57,7 @@ <BR><FORM ACTION="search/cust_main.cgi" METHOD="POST">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM> <FORM ACTION="search/cust_bill.cgi" METHOD="POST">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM> <FORM ACTION="search/cust_pay.cgi" METHOD="POST">Check # <INPUT TYPE="text" NAME="payinfo" SIZE="8"><INPUT TYPE="hidden" NAME="payby" VALUE="BILL"><INPUT TYPE="submit" VALUE="Search"></FORM> - <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg.html">Packages (by next bill date range)</A> + <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg_report.cgi">Packages (by next bill date range)</A> <BR><BR>Invoice reports <UL> <LI><a href="search/cust_bill_event.html">Invoice event errors (failed credit cards)</a> @@ -68,9 +69,10 @@ <LI>all invoices (<A HREF="search/cust_bill.cgi?invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?date">by date</A>) (<A HREF="search/cust_bill.cgi?custnum">by customer number</A>) </UL> <A HREF="search/report_cust_pay.html">Payment report (by type and/or date range)</A> - <BR><BR>Financial reports + <BR><BR><A HREF="search/report_receivables.cgi">Accounts Receivable Aging Summary</A> + <BR><BR><A HREF="search/report_prepaid_income.html">Prepaid Income (Unearned Revenue) Report</A> + <BR><BR>(old) Financial reports (being rewritten) <UL> - <LI> <A HREF="search/report_receivables.cgi">current receivables</A> <LI> <A HREF="search/report_tax.html">tax reports</A> <LI> <A HREF="search/report_cc.html">credit card receipts</A> <LI> <A HREF="search/report_credit.html">credit memos</A> @@ -121,34 +123,17 @@ Packages <UL> <LI><A HREF="search/cust_pkg.cgi?pkgnum">all packages (by package number)</A> - <LI><A HREF="search/cust_pkg.cgi?SUSP_pkgnum">suspended packages (by package number)</A> + <LI><A HREF="search/cust_pkg.cgi?magic=suspended">suspended packages (by package number)</A> <LI><A HREF="search/cust_pkg.cgi?APKG_pkgnum">packages with unconfigured services (by package number)</A> - <LI><A HREF="search/cust_pkg.html">packages (by next bill date range)</A> - </UL> - <A HREF="browse/part_pkg.cgi?active=1">Package definitions (by number of active packages)</A> - <BR><BR>Invoices - <UL> - <LI><a href="search/cust_bill_event.html">Invoice event errors (failed credit cards)</a> - <LI>open invoices (<A HREF="search/cust_bill.cgi?OPEN_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN_custnum">by customer number</A>) - <LI>30 day open invoices (<A HREF="search/cust_bill.cgi?OPEN30_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN30_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN30_custnum">by customer number</A>) - <LI>60 day open invoices (<A HREF="search/cust_bill.cgi?OPEN60_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN60_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN60_custnum">by customer number</A>) - <LI>90 day open invoices (<A HREF="search/cust_bill.cgi?OPEN90_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN90_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN90_custnum">by customer number</A>) - <LI>120 day open invoices (<A HREF="search/cust_bill.cgi?OPEN120_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN120_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN120_custnum">by customer number</A>) - <LI>all invoices (<A HREF="search/cust_bill.cgi?invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?date">by date</A>) (<A HREF="search/cust_bill.cgi?custnum">by customer number</A>) + <LI><A HREF="search/cust_pkg_report.cgi">packages (by next bill date range)</A> </UL> - <A HREF="search/report_cust_pay.html">Payment Report (by type and/or date range)</A> - <BR><BR>Financial reports - <UL> - <LI> <A HREF="search/report_receivables.cgi">current receivables</A> - <LI> <A HREF="search/report_tax.html">tax reports</A> - <LI> <A HREF="search/report_cc.html">credit card receipts</A> - <LI> <A HREF="search/report_credit.html">credit memos</A> - </UL> + <A HREF="browse/part_pkg.cgi?active=1">Package definitions (by number of active packages)</A><BR><BR> + <A HREF="browse/part_svc.cgi?active=1">Service definitions (by number of active services)</A><BR><BR> Customers <UL> <LI><A HREF="search/cust_main-otaker.cgi">Search customers by order-taker</A> </UL> - <FORM ACTION="search/sql.cgi" METHOD="POST">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM> + <FORM ACTION="search/sql.html" METHOD="POST">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM> <BR> </TD></TR> @@ -171,6 +156,7 @@ <BR><A HREF="browse/queue.cgi">View pending job queue</A> <BR><A HREF="misc/cust_main-import.cgi">Batch import customers from CSV file</A> <BR><A HREF="misc/cust_main-import_charges.cgi">Batch import charges from CSV file</A> + <BR><A HREF="misc/dump.cgi">Download database dump</A> <BR><BR><CENTER><HR WIDTH="94%" NOSHADE></CENTER><BR> <A NAME="config" HREF="config/config-view.cgi">Configuration</a><!-- - <font size="+2" color="#ff0000">start here</font> --> <BR><BR><A NAME="admin">Administration</a> @@ -195,7 +181,7 @@ <LI><A HREF="browse/cust_main_county.cgi">View/Edit locales and tax rates</A> - Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each. - <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit Access Numbers</A> + <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit access numbers</A> - Points of Presence <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices <LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels. diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi index 526e128a4..257c3384f 100755 --- a/httemplate/misc/cust_main-cancel.cgi +++ b/httemplate/misc/cust_main-cancel.cgi @@ -7,8 +7,8 @@ my $custnum = $1; my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); -my $error = $cust_main->cancel; -eidiot($error) if $error; +my @errors = $cust_main->cancel; +eidiot(join(' / ', @errors)) if scalar(@errors); #print $cgi->redirect($p. "view/cust_main.cgi?". $cust_main->custnum); print $cgi->redirect($p); diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi new file mode 100755 index 000000000..30de04d27 --- /dev/null +++ b/httemplate/misc/delete-cust_credit.cgi @@ -0,0 +1,16 @@ +<% + +#untaint crednum +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/ || die "Illegal crednum"; +my $crednum = $1; + +my $cust_credit = qsearchs('cust_credit',{'crednum'=>$crednum}); +my $custnum = $cust_credit->custnum; + +my $error = $cust_credit->delete; +eidiot($error) if $error; + +print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); + +%> diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi new file mode 100644 index 000000000..306ef5d63 --- /dev/null +++ b/httemplate/misc/download-batch.cgi @@ -0,0 +1,16 @@ +<% + +#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes +http_header('Content-Type' => 'text/plain' ); + +for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } + qsearch('cust_pay_batch', {} ) +) { + +$cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; +my( $mon, $y ) = ( $2, $1 ); +$mon = "0$mon" if $mon < 10; +my $exp = "$mon$y"; + +%>,,,,<%= $cust_pay_batch->cardnum %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %> +<% } %> diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi new file mode 100644 index 000000000..dc1323bb3 --- /dev/null +++ b/httemplate/misc/dump.cgi @@ -0,0 +1,19 @@ +<% + if ( driver_name =~ /^Pg$/ ) { + my $dbname = (split(':', datasrc))[2]; + if ( $dbname =~ /[;=]/ ) { + my %elements = map { /^(\w+)=(.*)$/; $1=>$2 } split(';', $dbname); + $dbname = $elements{'dbname'}; + } + open(DUMP,"pg_dump $dbname |"); + } else { + eidiot "don't (yet) know how to dump ". driver_name. " databases\n"; + } + + http_header('Content-Type' => 'text/plain' ); + + while (<DUMP>) { + print $_; + } + close DUMP; +%> diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi new file mode 100755 index 000000000..7ab1613ee --- /dev/null +++ b/httemplate/misc/email-invoice.cgi @@ -0,0 +1,23 @@ +<% + +my $conf = new FS::Conf; + +#untaint invnum +my($query) = $cgi->keywords; +$query =~ /^(\d*)$/; +my $invnum = $1; +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +die "Can't find invoice!\n" unless $cust_bill; + +my $error = send_email( + 'from' => $conf->config('invoice_from'), + 'to' => [ grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ], + 'subject' => 'Invoice', + 'body' => [ $cust_bill->print_text ], +); +eidiot($error) if $error; + +my $custnum = $cust_bill->getfield('custnum'); +print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); + +%> diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi index a5500bff2..144f6156a 100755 --- a/httemplate/misc/print-invoice.cgi +++ b/httemplate/misc/print-invoice.cgi @@ -11,13 +11,19 @@ my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; open(LPR,"|$lpr") or die "Can't open $lpr: $!"; - print LPR $cust_bill->print_text; #( date ) + + if ( $conf->exists('invoice_latex') ) { + print LPR $cust_bill->print_ps; #( date ) + } else { + print LPR $cust_bill->print_text; #( date ) + } + close LPR or die $! ? "Error closing $lpr: $!" : "Exit status $? from $lpr"; my $custnum = $cust_bill->getfield('custnum'); -print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum#history"); +print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); %> diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi index 2939c8fb2..59d236f64 100644 --- a/httemplate/misc/process/meta-import.cgi +++ b/httemplate/misc/process/meta-import.cgi @@ -116,8 +116,8 @@ function SafeOnsubmit() { #hashmaker widget sub hashmaker { my($name, $from, $to, $labelfrom, $labelto) = @_; - $fromsize = scalar(@$from); - $tosize = scalar(@$to); + my $fromsize = scalar(@$from); + my $tosize = scalar(@$to); "<TABLE><TR><TH>$labelfrom</TH><TH>$labelto</TH></TR><TR><TD>". qq!<SELECT NAME="${name}_from" SIZE=$fromsize>\n!. join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$from ). diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi new file mode 100644 index 000000000..cc5346606 --- /dev/null +++ b/httemplate/misc/upload-batch.cgi @@ -0,0 +1,29 @@ +<% + + my $fh = $cgi->upload('batch_results'); + my $filename = $cgi->param('batch_results'); + $filename =~ /^.*[\/\\]([^\/\\]+)$/ or die; + my $paybatch = $1; + + my $error = defined($fh) + ? FS::cust_pay_batch::import_results( { + 'filehandle' => $fh, + 'format' => $cgi->param('format'), + 'paybatch' => $paybatch, + } ) + : 'No file'; + + if ( $error ) { + %> + <!-- mason kludge --> + <% + eidiot($error); +# $cgi->param('error', $error); +# print $cgi->redirect( "${p}cust_main-import.cgi + } else { + %> + <!-- mason kludge --> + <%= header('Batch results upload sucessful') %> <% + } +%> + diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi index b76f66b76..ec952ea5b 100644 --- a/httemplate/search/cust_bill_event.cgi +++ b/httemplate/search/cust_bill_event.cgi @@ -7,7 +7,7 @@ $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/; my $beginning = str2time($1) || 0; $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/; -my $ending = str2time($1) + 86400; +my $ending = str2time($1) + 86399; my @cust_bill_event = sort { $a->_date <=> $b->_date } diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html index 9f39db914..d48f1d08f 100755 --- a/httemplate/search/cust_main-quickpay.html +++ b/httemplate/search/cust_main-quickpay.html @@ -7,27 +7,28 @@ Quick payment entry </FONT> <BR><BR> + <A HREF="../">Main Menu</A><BR><BR> <FORM ACTION="cust_main.cgi" METHOD="post"> <INPUT TYPE="hidden" NAME="quickpay" VALUE="yes"> <INPUT TYPE="checkbox" NAME="last_on" CHECKED> Search for <B>last name</B>: <INPUT TYPE="text" NAME="last_text"> using search method: <SELECT NAME="last_type"> - <OPTION SELECTED>All + <OPTION>All <OPTION>Fuzzy <OPTION>Substring - <OPTION>Exact + <OPTION SELECTED>Exact </SELECT> <P><INPUT TYPE="checkbox" NAME="company_on" CHECKED> Search for <B>company</B>: <INPUT TYPE="text" NAME="company_text"> using search methods: <SELECT NAME="company_type"> - <OPTION SELECTED>All + <OPTION>All <OPTION>Fuzzy <OPTION>Substring - <OPTION>Exact + <OPTION SELECTED>Exact </SELECT> - <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while. Please be patient. + <P><INPUT TYPE="submit" VALUE="Search"> </FORM> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 5b39a09f2..50d367e1e 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -50,6 +50,7 @@ my $total = 0; my(@cust_main, $sortby, $orderby); if ( $cgi->param('browse') || $cgi->param('otaker_on') + || $cgi->param('agentnum_on') ) { my %search = (); @@ -73,6 +74,9 @@ if ( $cgi->param('browse') if ( $cgi->param('otaker_on') ) { $cgi->param('otaker') =~ /^(\w{1,32})$/ or eidiot "Illegal otaker\n"; $search{otaker} = $1; + } elsif ( $cgi->param('agentnum_on') ) { + $cgi->param('agentnum') =~ /^(\d+)$/ or eidiot "Illegal agentnum\n"; + $search{agentnum} = $1; } else { die "unknown query..."; } @@ -112,6 +116,7 @@ if ( $cgi->param('browse') AND (temp1_$$.count > 0 OR temp2_$$.count = 0 ) "; + } else { $ncancelled = " 0 < ( SELECT COUNT(*) FROM cust_pkg @@ -124,15 +129,32 @@ if ( $cgi->param('browse') WHERE cust_pkg.custnum = cust_main.custnum ) "; - } - + } + } + + my $cancelled = ''; + if ( $cgi->param('cancelled') ) { + $cancelled = " + 0 = ( SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + AND ( cust_pkg.cancel IS NULL + OR cust_pkg.cancel = 0 + ) + ) + AND 0 < ( SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + ) + "; } #EWWWWWW my $qual = join(' AND ', map { "$_ = ". dbh->quote($search{$_}) } keys %search ); - if ( $ncancelled ) { + if ( $cancelled ) { + $qual .= ' AND ' if $qual; + $qual .= $cancelled; + } elsif ( $ncancelled ) { $qual .= ' AND ' if $qual; $qual .= $ncancelled; } @@ -150,21 +172,22 @@ if ( $cgi->param('browse') $total = $sth->fetchrow_arrayref->[0]; - if ( $ncancelled ) { + my $rqual = $cancelled || $ncancelled; + if ( $rqual ) { if ( %search ) { - $ncancelled = " AND $ncancelled"; + $rqual = " AND $rqual"; } else { - $ncancelled = " WHERE $ncancelled"; + $rqual = " WHERE $rqual"; } } my @just_cust_main; if ( driver_name eq 'mysql' ) { @just_cust_main = qsearch('cust_main', \%search, 'cust_main.*', - ",temp1_$$,temp2_$$ $ncancelled $orderby $limit"); + ",temp1_$$,temp2_$$ $rqual $orderby $limit"); } else { @just_cust_main = qsearch('cust_main', \%search, '', - "$ncancelled $orderby $limit" ); + "$rqual $orderby $limit" ); } if ( driver_name eq 'mysql' ) { my $sql = "DROP TABLE temp1_$$,temp2_$$;"; @@ -213,9 +236,12 @@ if ( $cgi->param('browse') } @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main - if $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledcustomers') - && ! $cgi->param('showcancelledcustomers') ); + if ! $cgi->param('cancelled') + && ( + $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me + || ( $conf->exists('hidecancelledcustomers') + && ! $cgi->param('showcancelledcustomers') ) + ); my %saw = (); @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; @@ -277,19 +303,22 @@ if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { } } #end pager - - if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me - || ( $conf->exists('hidecancelledcustomers') - && ! $cgi->param('showcancelledcustomers') - ) - ) { - $cgi->param('showcancelledcustomers', 1); - $cgi->param('offset', 0); - print qq!( <a href="!. $cgi->self_url. qq!">show cancelled customers</a> )!; - } else { - $cgi->param('showcancelledcustomers', 0); - $cgi->param('offset', 0); - print qq!( <a href="!. $cgi->self_url. qq!">hide cancelled customers</a> )!; + + unless ( $cgi->param('cancelled') ) { + if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me + || ( $conf->exists('hidecancelledcustomers') + && ! $cgi->param('showcancelledcustomers') + ) + ) { + $cgi->param('showcancelledcustomers', 1); + $cgi->param('offset', 0); + print qq!( <a href="!. $cgi->self_url. qq!">show!; + } else { + $cgi->param('showcancelledcustomers', 0); + $cgi->param('offset', 0); + print qq!( <a href="!. $cgi->self_url. qq!">hide!; + } + print ' cancelled customers</a> )'; } if ( $cgi->param('referral_custnum') ) { $cgi->param('referral_custnum') =~ /^(\d+)$/ diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi index e4dba01e9..51dd3b340 100755 --- a/httemplate/search/cust_pay.cgi +++ b/httemplate/search/cust_pay.cgi @@ -5,27 +5,53 @@ my @cust_pay; if ( $cgi->param('magic') && $cgi->param('magic') eq '_date' ) { my %search; + my @search; + if ( $cgi->param('payby') ) { - $cgi->param('payby') =~ /^(CARD|CHEK|BILL)$/ + $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/ or die "illegal payby ". $cgi->param('payby'); $search{'payby'} = $1; + if ( $3 ) { + if ( $3 eq 'VisaMC' ) { + #avoid posix regexes for portability + push @search, " ( substring(payinfo from 1 for 1) = '4' ". + " OR substring(payinfo from 1 for 2) = '51' ". + " OR substring(payinfo from 1 for 2) = '52' ". + " OR substring(payinfo from 1 for 2) = '53' ". + " OR substring(payinfo from 1 for 2) = '54' ". + " OR substring(payinfo from 1 for 2) = '54' ". + " OR substring(payinfo from 1 for 2) = '55' ". + " ) "; + } elsif ( $3 eq 'Amex' ) { + push @search, " ( substring(payinfo from 1 for 2 ) = '34' ". + " OR substring(payinfo from 1 for 2 ) = '37' ". + " ) "; + } elsif ( $3 eq 'Discover' ) { + push @search, " substring(payinfo from 1 for 4 ) = '6011' "; + } else { + die "unknown card type $3"; + } + } } #false laziness with cust_pkg.cgi - my $range = ''; if ( $cgi->param('beginning') && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { my $beginning = str2time($1); - $range = " WHERE _date >= $beginning "; + push @search, "_date >= $beginning "; } if ( $cgi->param('ending') && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { - my $ending = str2time($1) + 86400; - $range .= ( $range ? ' AND ' : ' WHERE ' ). " _date <= $ending "; + my $ending = str2time($1) + 86399; + push @search, " _date <= $ending "; + } + my $search; + if ( @search ) { + $search = ( scalar(keys %search) ? ' AND ' : ' WHERE ' ). + join(' AND ', @search); } - $range =~ s/^\s*WHERE/ AND/ if scalar(keys %search) ; - @cust_pay = qsearch('cust_pay', \%search, '', $range ); + @cust_pay = qsearch('cust_pay', \%search, '', $search ); $sortby = \*date_sort; diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 8b2fd0ca0..45420f4c4 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -29,10 +29,18 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { } if ( $cgi->param('ending') && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { - my $ending = str2time($1) + 86400; + my $ending = str2time($1) + 86399; $range .= ( $range ? ' AND ' : ' WHERE ' ). " bill <= $ending "; } + $range .= ( $range ? 'AND ' : ' WHERE ' ). '( cancel IS NULL OR cancel = 0 )'; + + if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { + $range .= ( $range ? 'AND ' : ' WHERE ' ). + "$1 = ( SELECT agentnum FROM cust_main". + " WHERE cust_main.custnum = cust_pkg.custnum )"; + } + #false laziness with below my $statement = "SELECT COUNT(*) FROM cust_pkg $range"; warn $statement; @@ -46,10 +54,21 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { } else { my $qual = ''; - if ( $cgi->param('magic') && $cgi->param('magic') eq 'active' ) { + if ( $cgi->param('magic') && + $cgi->param('magic') =~ /^(active|suspended|canceled)$/ + ) { - $qual = 'WHERE ( susp IS NULL OR susp = 0 )'. - ' AND ( cancel IS NULL OR cancel = 0)'; + if ( $cgi->param('magic') eq 'active' ) { + $qual = 'WHERE ( susp IS NULL OR susp = 0 )'. + ' AND ( cancel IS NULL OR cancel = 0)'; + } elsif ( $cgi->param('magic') eq 'suspended' ) { + $qual = 'WHERE susp IS NOT NULL AND susp != 0'. + ' AND ( cancel IS NULL OR cancel = 0)'; + } elsif ( $cgi->param('magic') eq 'canceled' ) { + $qual = 'WHERE cancel IS NOT NULL AND cancel != 0'; + } else { + die "guru meditation #420"; + } $sortby = \*pkgnum_sort; @@ -61,12 +80,6 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { $sortby=\*pkgnum_sort; - } elsif ( $query eq 'SUSP_pkgnum' ) { - - $sortby=\*pkgnum_sort; - - $qual = 'WHERE susp IS NOT NULL AND susp != 0'; - } elsif ( $query eq 'APKG_pkgnum' ) { $sortby=\*pkgnum_sort; diff --git a/httemplate/search/cust_pkg.html b/httemplate/search/cust_pkg.html deleted file mode 100755 index bb0a5407c..000000000 --- a/httemplate/search/cust_pkg.html +++ /dev/null @@ -1,24 +0,0 @@ -<HTML> - <HEAD> - <TITLE>Packages</TITLE> - </HEAD> - <BODY> - <CENTER> - <H1>Packages</H1> - </CENTER> - <HR> - <FORM ACTION="cust_pkg.cgi" METHOD="post"> - <INPUT TYPE="hidden" NAME="magic" VALUE="bill"> - Return <B>packages</B> with next bill date: - from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i> - to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i> - - <P><INPUT TYPE="submit" VALUE="Get Report"> - - </FORM> - - <HR> - - </BODY> -</HTML> - diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi new file mode 100755 index 000000000..b31674540 --- /dev/null +++ b/httemplate/search/cust_pkg_report.cgi @@ -0,0 +1,63 @@ +<HTML> + <HEAD> + <TITLE>Packages</TITLE> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <H1>Packages</H1> + <FORM ACTION="cust_pkg.cgi" METHOD="post"> + <INPUT TYPE="hidden" NAME="magic" VALUE="bill"> + Return packages with next bill date:<BR><BR> + <TABLE> + <TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y", + button: "beginning_button", + align: "BR" + }); +</SCRIPT> + </TR> + <TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y", + button: "ending_button", + align: "BR" + }); +</SCRIPT> + </TR> +<% my %agent_search = dbdef->table('agent')->column('disabled') + ? ( 'disabled' => '' ) : (); + my @agents = qsearch( 'agent', \%agent_search ); + if ( scalar(@agents) == 1 ) { +%> + <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agents[0]->agentnum %>"> +<% } else { %> + + <TR> + <TD ALIGN="right">Agent: </TD> + <TD><SELECT NAME="agentnum"><OPTION VALUE="">(all) + <% foreach my $agent ( sort { $a->agent cmp $b->agent; } @agents) { %> + <OPTION VALUE="<%= $agent->agentnum %>"><%= $agent->agent %> + <% } %> + </TD> + </TR> +<% } %> + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + + </FORM> + + </BODY> +</HTML> + diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html new file mode 100644 index 000000000..fbedcaa26 --- /dev/null +++ b/httemplate/search/elements/search.html @@ -0,0 +1,59 @@ +<% + + my %opt = @_; + unless (exists($opt{'count_query'}) && length($opt{'count_query'})) { + ( $opt{'count_query'} = $opt{'query'} ) =~ + s/^\s*SELECT\s*(.*)\s+FROM\s/SELECT COUNT(*) FROM /i; + } + + my $conf = new FS::Conf; + my $maxrecords = $conf->config('maxsearchrecordsperpage'); + + my $limit = $maxrecords ? "LIMIT $maxrecords" : ''; + + my $offset = $cgi->param('offset') || 0; + $limit .= " OFFSET $offset" if $offset; + + my $count_sth = dbh->prepare($opt{'count_query'}) + or die "Error preparing $opt{'count_query'}: ". dbh->errstr; + $count_sth->execute + or die "Error executing $opt{'count_query'}: ". $count_sth->errstr; + my $total = $count_sth->fetchrow_arrayref->[0]; + + my $sth = dbh->prepare("$opt{'query'} $limit") + or die "Error preparing $opt{'query'}: ". dbh->errstr; + $sth->execute + or die "Error executing $opt{'query'}: ". $sth->errstr; + + #can get # of rows without fetching them all? + my $rows = $sth->fetchall_arrayref; + +%> +<!-- mason kludge --> +<% my $pager = include ( '/elements/pager.html', + 'offset' => $offset, + 'num_rows' => scalar(@$rows), + 'total' => $total, + 'maxrecords' => $maxrecords, + ); +%> + +<%= $total %> total <%= $opt{'name'} %><BR><BR><%= $pager %> +<%= include( '/elements/table.html' ) %> + <TR> + <% foreach ( @{$sth->{NAME}} ) { %> + <TH><%= $_ %></TH> + <% } %> + </TR> + <% foreach my $row ( @$rows ) { %> + <TR> + <% foreach ( @$row ) { %> + <TD><%= $_ %></TD> + <% } %> + </TR> + <% } %> + +</TABLE> +<%= $pager %> +</BODY> +</HTML> diff --git a/httemplate/search/report_cc.html b/httemplate/search/report_cc.html index 8653dcc69..595a7b150 100755 --- a/httemplate/search/report_cc.html +++ b/httemplate/search/report_cc.html @@ -1,23 +1,43 @@ <HTML> <HEAD> <TITLE>Credit Card Receipt Report Criteria</TITLE> - </HEAD> - <BODY> - <CENTER> - <H1>Credit Card Receipt Report Criteria</H1> - </CENTER> - <HR> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <H1>Credit Card Receipt Report Criteria</H1> <FORM ACTION="report_cc.cgi" METHOD="post"> Return <B>credit card receipt report</B> for period: - from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i> - to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i> + <TABLE> + <TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y", + button: "beginning_button", + align: "BR" + }); +</SCRIPT> + </TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y", + button: "ending_button", + align: "BR" + }); +</SCRIPT> + </TR> + </TABLE> - <P><INPUT TYPE="submit" VALUE="Get Report"> + <BR><INPUT TYPE="submit" VALUE="Get Report"> </FORM> - - <HR> - </BODY> </HTML> diff --git a/httemplate/search/report_credit.html b/httemplate/search/report_credit.html index df9b9581f..11cb32ed8 100755 --- a/httemplate/search/report_credit.html +++ b/httemplate/search/report_credit.html @@ -1,23 +1,43 @@ <HTML> <HEAD> <TITLE>In House Credit Report Criteria</TITLE> - </HEAD> - <BODY> - <CENTER> - <H1>In House Credit Report Criteria</H1> - </CENTER> - <HR> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <H1>In House Credit Report Criteria</H1> <FORM ACTION="report_credit.cgi" METHOD="post"> Return <B>in house credit report</B> for period: - from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i> - to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i> + <TABLE> + <TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y", + button: "beginning_button", + align: "BR" + }); +</SCRIPT> + </TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y", + button: "ending_button", + align: "BR" + }); +</SCRIPT> + </TR> + </TABLE> - <P><INPUT TYPE="submit" VALUE="Get Report"> + <BR><INPUT TYPE="submit" VALUE="Get Report"> </FORM> - - <HR> - </BODY> </HTML> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html index 93053e1ee..1b30685dc 100644 --- a/httemplate/search/report_cust_pay.html +++ b/httemplate/search/report_cust_pay.html @@ -1,24 +1,54 @@ <HTML> <HEAD> <TITLE>Payment report criteria</TITLE> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> </HEAD> - <BODY> - <CENTER> - <H1>Payment report criteria</H1> - </CENTER> - <HR> + <BODY BGCOLOR="#e8e8e8"> + <H1>Payment report criteria</H1> <FORM ACTION="cust_pay.cgi" METHOD="post"> <INPUT TYPE="hidden" NAME="magic" VALUE="_date"> - Return <SELECT NAME="payby"> - <OPTION VALUE="">all</OPTION> - <OPTION VALUE="CARD">credit card</OPTION> - <OPTION VALUE="CHEK">electronic check (ACH)</OPTION> - <OPTION VALUE="BILL">check/cash</OPTION> - </SELECT> payments for period<BR> - from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i> - to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i> - <P><INPUT TYPE="submit" VALUE="Get Report"> + <TABLE> + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD><SELECT NAME="payby"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card (all)</OPTION> + <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION> + <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION> + <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + <OPTION VALUE="BILL">check / cash</OPTION> + </SELECT> + </TD> + </TR> + <TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y", + button: "beginning_button", + align: "BR" + }); +</SCRIPT> + </TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y", + button: "ending_button", + align: "BR" + }); +</SCRIPT> + </TR> + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> </FORM> - <HR> </BODY> </HTML> diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi new file mode 100644 index 000000000..eb8bbb55e --- /dev/null +++ b/httemplate/search/report_prepaid_income.cgi @@ -0,0 +1,75 @@ +<!-- mason kludge --> +<% + + #doesn't yet deal with daily/weekly packages + + #needs to be re-written in sql for efficiency + + my $now = $cgi->param('date') && str2time($cgi->param('date')) || time; + $now =~ /^(\d+)$/ or die "unparsable date?"; + $now = $1; + + my %prepaid; + + my @cust_bill_pkg = + grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ } + qsearch( 'cust_bill_pkg', { + 'recur' => { op=>'!=', value=>0 }, + 'edate' => { op=>'>', value=>$now }, + }, ); + + foreach my $cust_bill_pkg ( @cust_bill_pkg ) { + + #conceptual false laziness w/texas tax exempt_amount stuff in + #FS::cust_main::bill + + my $freq = $cust_bill_pkg->cust_pkg->part_pkg->freq; + my $per_month = sprintf("%.2f", $cust_bill_pkg->recur / $freq); + + my($mon, $year) = (localtime($cust_bill_pkg->sdate) )[4,5]; + $mon+=2; $year+=1900; + + foreach my $which_month ( 2 .. $freq ) { + until ( $mon < 13 ) { $mon -= 12; $year++; } + $prepaid{"$year-$mon"} += $per_month; + $mon++; + } + + } + + my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); + +%> + +<%= header( 'Prepaid Income (Unearned Revenue) Report', + menubar( 'Main Menu'=>$p, ) ) %> +<%= table() %> +<% + + my $total = 0; + + my ($now_mon, $now_year) = (localtime($now))[4,5]; + $now_mon+=2; $now_year+=1900; + until ( $now_mon < 13 ) { $now_mon -= 12; $now_year++; } + + my $subseq = 0; + for my $year ( $now_year .. 2037 ) { + for my $mon ( ( $subseq++ ? 1 : $now_mon ) .. 12 ) { + if ( $prepaid{"$year-$mon"} ) { + $total += $prepaid{"$year-$mon"}; + %> <TR><TD ALIGN="right"><%= $mon[$mon-1]. ' '. $year %></TD> + <TD ALIGN="right"> + <%= sprintf("%.2f", $prepaid{"$year-$mon"} ) %> + </TD> + </TR> + <% + } + } + + } + +%> +<TR><TH>Total</TH><TD ALIGN="right"><%= sprintf("%.2f", $total) %></TD></TR> +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html new file mode 100644 index 000000000..b85a481be --- /dev/null +++ b/httemplate/search/report_prepaid_income.html @@ -0,0 +1,17 @@ +<HTML> + <HEAD> + <TITLE>Prepaid Income (Unearned Revenue) Report</TITLE> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <H1>Prepaid Income (Unearned Revenue) Report</H1> + <FORM ACTION="report_prepaid_income.cgi" METHOD="post"> + Prepaid income (unearned revenue) as of <INPUT TYPE="text" NAME="date" VALUE="now"> + <INPUT TYPE="submit" VALUE="Generate report"> + </BODY> +</HTML> + <TABLE> + diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index fdd3779a9..ad353a1b3 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -1,19 +1,158 @@ <!-- mason kludge --> <% -my $user = getotaker; + my $charged = <<END; + sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) + ,0 + ) -print header('Current Receivables Report Results'); + ) +END -open (REPORT, "freeside-receivables-report -v $user |"); + my $owed_cols = <<END; + coalesce( + ( select $charged from cust_bill + where cust_bill._date > extract(epoch from now())-2592000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_0_30, -print '<PRE>'; -while(<REPORT>) { - print $_; -} -print '</PRE>'; + coalesce( + ( select $charged from cust_bill + where cust_bill._date > extract(epoch from now())-5184000 + and cust_bill._date <= extract(epoch from now())-2592000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_30_60, -print '</BODY></HTML>'; + coalesce( + ( select $charged from cust_bill + where cust_bill._date > extract(epoch from now())-7776000 + and cust_bill._date <= extract(epoch from now())-5184000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_60_90, -%> + coalesce( + ( select $charged from cust_bill + where cust_bill._date <= extract(epoch from now())-7776000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_90_plus, + + coalesce( + ( select $charged from cust_bill + where cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_total +END + + my $recurring = <<END; + 0 < ( select freq from part_pkg + where cust_pkg.pkgpart = part_pkg.pkgpart ) +END + + my $packages_cols = <<END; + + ( select count(*) from cust_pkg + where cust_main.custnum = cust_pkg.custnum + and $recurring + and ( cancel = 0 or cancel is null ) + ) as uncancelled_pkgs, + + ( select count(*) from cust_pkg + where cust_main.custnum = cust_pkg.custnum + and $recurring + and ( cancel = 0 or cancel is null ) + and ( susp = 0 or susp is null ) + ) as active_pkgs + +END + + my $sql = <<END; +select *, $owed_cols, $packages_cols from cust_main +where 0 < + coalesce( + ( select $charged from cust_bill + where cust_main.custnum = cust_bill.custnum + ) + ,0 + ) + +order by lower(company), lower(last) + +END + + my $total_sql = "select $owed_cols"; + + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + + my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; + $total_sth->execute or die $total_sth->errstr; + +%> +<%= header('Accounts Receivable Aging Summary', menubar( 'Main Menu'=>$p, ) ) %> +<%= table() %> + <TR> + <TH>Customer</TH> + <TH>Status</TH> + <TH>0-30</TH> + <TH>30-60</TH> + <TH>60-90</TH> + <TH>90+</TH> + <TH>Total</TH> + </TR> +<% while ( my $row = $sth->fetchrow_hashref() ) { + my $status = 'Cancelled'; + my $statuscol = 'FF0000'; + if ( $row->{uncancelled_pkgs} ) { + $status = 'Suspended'; + $statuscol = 'FF9900'; + if ( $row->{active_pkgs} ) { + $status = 'Active'; + $statuscol = '00CC00'; + } + } +%> + <TR> + <TD><A HREF="<%= $p %>view/cust_main.cgi?<%= $row->{'custnum'} %>"><%= $row->{'custnum'} %>: + <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %><%= $row->{'last'}. ', '. $row->{'first'} %><%= $row->{'company'} ? ')' : '' %></A> + </TD> + <TD><B><FONT SIZE=-1 COLOR="#<%= $statuscol %>"><%= $status %></FONT></B></TD> + <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD> + <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD> + <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD> + <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD> + <TD ALIGN="right"><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></TD> + </TR> +<% } %> +<% my $row = $total_sth->fetchrow_hashref(); %> + <TR> + <TD COLSPAN=6> </TD> + </TR> + <TR> + <TD COLSPAN=2><I>Total</I></TD> + <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD> + <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD> + <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD> + <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD> + <TD ALIGN="right"><I><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></I></TD> + </TR> +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index 7bf681b42..7bc35e3ed 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -1,23 +1,43 @@ <HTML> <HEAD> <TITLE>Tax Report Criteria</TITLE> - </HEAD> - <BODY> - <CENTER> - <H1>Tax Report Criteria</H1> - </CENTER> - <HR> + <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <H1>Tax Report Criteria</H1> <FORM ACTION="report_tax.cgi" METHOD="post"> - Return <B>tax report</B> for period: - from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i> - to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i> + Return <B>tax report</B> for period: + <TABLE> + <TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y", + button: "beginning_button", + align: "BR" + }); +</SCRIPT> + </TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y", + button: "ending_button", + align: "BR" + }); +</SCRIPT> + </TR> + </TABLE> - <P><INPUT TYPE="submit" VALUE="Get Report"> + <BR><INPUT TYPE="submit" VALUE="Get Report"> </FORM> - - <HR> - </BODY> </HTML> diff --git a/httemplate/search/sql.cgi b/httemplate/search/sql.cgi deleted file mode 100755 index b83ef039f..000000000 --- a/httemplate/search/sql.cgi +++ /dev/null @@ -1,76 +0,0 @@ -<% - -my $conf = new FS::Conf; -my $maxrecords = $conf->config('maxsearchrecordsperpage'); - -my $limit = ''; -$limit .= "LIMIT $maxrecords" if $maxrecords; - -my $offset = $cgi->param('offset') || 0; -$limit .= " OFFSET $offset" if $offset; - -my $total; - -my $sql = $cgi->param('sql'); -$sql =~ s/^\s*SELECT//i; - -my $count_sql = $sql; -$count_sql =~ s/^(.*)\s+FROM\s/COUNT(*) FROM /i; - -my $sth = dbh->prepare("SELECT $count_sql") - or eidiot dbh->errstr. " doing $count_sql\n"; -$sth->execute or eidiot "Error executing \"$count_sql\": ". $sth->errstr; - -$total = $sth->fetchrow_arrayref->[0]; - -my $sth = dbh->prepare("SELECT $sql $limit") - or eidiot dbh->errstr. " doing $sql\n"; -$sth->execute or eidiot "Error executing \"$sql\": ". $sth->errstr; -my $rows = $sth->fetchall_arrayref; - -%> -<!-- mason kludge --> -<% - - #begin pager - my $pager = ''; - if ( $total != scalar(@$rows) && $maxrecords ) { - unless ( $offset == 0 ) { - $cgi->param('offset', $offset - $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Previous</FONT></B></A> '; - } - my $poff; - my $page; - for ( $poff = 0; $poff < $total; $poff += $maxrecords ) { - $page++; - if ( $offset == $poff ) { - $pager .= qq!<FONT SIZE="+2">$page</FONT> !; - } else { - $cgi->param('offset', $poff); - $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !; - } - } - unless ( $offset + $maxrecords > $total ) { - $cgi->param('offset', $offset + $maxrecords); - $pager .= '<A HREF="'. $cgi->self_url. - '"><B><FONT SIZE="+1">Next</FONT></B></A> '; - } - } - #end pager - - print header('Query Results', menubar('Main Menu'=>$p) ). - "$total total rows<BR><BR>$pager". table(). - "<TR>"; - print "<TH>$_</TH>" foreach @{$sth->{NAME}}; - print "</TR>"; - - foreach $row ( @$rows ) { - print "<TR>"; - print "<TD>$_</TD>" foreach @$row; - print "</TR>"; - } - - print "</TABLE>$pager</BODY></HTML>"; - -%> diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html new file mode 100644 index 000000000..7d7fc0890 --- /dev/null +++ b/httemplate/search/sql.html @@ -0,0 +1,12 @@ +<%= include( '/elements/header.html', 'Query Results', + include( '/elements/menubar.html', 'Main Menu' => $p ) + ) +%> + +<%= include( 'elements/search.html', + 'name' => 'rows', + 'query' => 'SELECT '. ( $cgi->param('sql') + || eidiot('Empty query') ), + ) +%> + diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index e43f4f79b..1e4a03d84 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -46,14 +46,31 @@ if ( $query eq 'svcnum' ) { $orderby = "ORDER BY ${tblname}username"; } elsif ( $query eq 'uid' ) { $sortby=\*uid_sort; - $orderby = ( $unlinked ? 'AND' : 'WHERE' ). + $orderby = ( $unlinked ? ' AND' : ' WHERE' ). " ${tblname}uid IS NOT NULL ORDER BY ${tblname}uid"; +} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) { + $unlinked .= ( $unlinked ? 'AND' : 'WHERE' ). + " popnum = $1"; + $sortby=\*username_sort; + $orderby = "ORDER BY ${tblname}username"; +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + $unlinked .= ( $unlinked ? ' AND' : ' WHERE' ). + " $1 = ( SELECT svcpart FROM cust_svc ". + " WHERE cust_svc.svcnum = svc_acct.svcnum ) "; + $sortby=\*uid_sort; + #$sortby=\*svcnum_sort; } else { $sortby=\*uid_sort; @svc_acct = @{&usernamesearch}; } -if ( $query eq 'svcnum' || $query eq 'username' || $query eq 'uid' ) { + +if ( $query eq 'svcnum' + || $query eq 'username' + || $query eq 'uid' + || $cgi->param('popnum') =~ /^(\d+)$/ + || $cgi->param('svcpart') =~ /^(\d+)$/ + ) { my $statement = "SELECT COUNT(*) FROM svc_acct $unlinked"; my $sth = dbh->prepare($statement) diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index c0acf1143..948b1d9ae 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -23,6 +23,13 @@ if ( $query eq 'svcnum' ) { 'svcnum' => $_->svcnum, 'pkgnum' => '', }), qsearch('svc_domain',{}); +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + @svc_domain = + qsearch( 'svc_domain', {}, '', + " WHERE $1 = ( SELECT svcpart FROM cust_svc ". + " WHERE cust_svc.svcnum = svc_domain.svcnum ) " + ); + $sortby=\*svcnum_sort; } else { $cgi->param('domain') =~ /^([\w\-\.]+)$/; my($domain)=$1; diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi new file mode 100755 index 000000000..10094bc99 --- /dev/null +++ b/httemplate/search/svc_forward.cgi @@ -0,0 +1,79 @@ +<% + +my $conf = new FS::Conf; + +my($query)=$cgi->keywords; +$query ||= ''; #to avoid use of unitialized value errors +my(@svc_forward,$sortby); +if ( $query eq 'svcnum' ) { + $sortby=\*svcnum_sort; + @svc_forward=qsearch('svc_forward',{}); +} else { + eidiot('unimplemented'); +} + +if ( scalar(@svc_forward) == 1 ) { + print $cgi->redirect(popurl(2). "view/svc_forward.cgi?". $svc_forward[0]->svcnum); + #exit; +} elsif ( scalar(@svc_forward) == 0 ) { +%> +<!-- mason kludge --> +<% + eidiot "No matching forwards found!\n"; +} else { +%> +<!-- mason kludge --> +<% + my $total = scalar(@svc_forward); + print header("Mail forward Search Results",''), <<END; + + $total matching mail forwards found + <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> + <TR> + <TH>Service #<BR><FONT SIZE=-1>(click to view forward)</FONT></TH> + <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH> + <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH> + </TR> +END + + foreach my $svc_forward ( + sort $sortby (@svc_forward) + ) { + my $svcnum = $svc_forward->svcnum; + + my $src = $svc_forward->src; + $src = "<I>(anything)</I>$src" if $src =~ /^@/; + if ( $svc_forward->srcsvc_acct ) { + $src = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->srcsvc. '">'. + $svc_forward->srcsvc_acct->email. '</A>'; + } + + my $dst = $svc_forward->dst; + if ( $svc_forward->dstsvc_acct ) { + $dst = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->dstsvc. '">'. + $svc_forward->dstsvc_acct->email. '</A>'; + } + + print <<END; + <TR> + <TD><A HREF="${p}view/svc_forward.cgi?$svcnum">$svcnum</A></TD> + <TD>$src</TD> + <TD>$dst</TD> + </TR> +END + + } + + print <<END; + </TABLE> + </BODY> +</HTML> +END + +} + +sub svcnum_sort { + $a->getfield('svcnum') <=> $b->getfield('svcnum'); +} + +%> diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi new file mode 100755 index 000000000..7aa69100d --- /dev/null +++ b/httemplate/view/cust_bill-pdf.cgi @@ -0,0 +1,13 @@ +<% + +#untaint invnum +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $invnum = $1; + +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +die "Invoice #$invnum not found!" unless $cust_bill; + +http_header('Content-Type' => 'application/pdf' ); +%> +<%= $cust_bill->print_pdf %> diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi new file mode 100755 index 000000000..03340a16f --- /dev/null +++ b/httemplate/view/cust_bill-ps.cgi @@ -0,0 +1,13 @@ +<% + +#untaint invnum +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $invnum = $1; + +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +die "Invoice #$invnum not found!" unless $cust_bill; + +http_header('Content-Type' => 'application/postscript' ); +%> +<%= $cust_bill->print_ps %> diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index 53d7bc051..50ee8b36c 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -20,7 +20,20 @@ print header('Invoice View', menubar( print qq!<A HREF="${p}edit/cust_pay.cgi?$invnum">Enter payments (check/cash) against this invoice</A> | ! if $cust_bill->owed > 0; -print qq!<A HREF="${p}misc/print-invoice.cgi?$invnum">Reprint this invoice</A>!. '<BR><BR>'; +print qq!<A HREF="${p}misc/print-invoice.cgi?$invnum">Reprint this invoice</A>!; +if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { + print qq! | <A HREF="${p}misc/email-invoice.cgi?$invnum">!. + qq!Re-email this invoice</A>!; +} + +print '<BR><BR>'; + +my $conf = new FS::Conf; +if ( $conf->exists('invoice_latex') ) { + print menubar( + 'View typeset invoice' => "${p}view/cust_bill-pdf.cgi?$invnum", + ), '<BR><BR>'; +} #false laziness with search/cust_bill_event.cgi diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index c36c9e265..ee5f86973 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -76,7 +76,7 @@ print '<TD VALIGN="top">'; print "Billing address", &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2), - '<TR><TD ALIGN="right">Contact name</TD>', + '<TR><TD ALIGN="right">Contact name</TD>', '<TD COLSPAN=3 BGCOLOR="#ffffff">', $cust_main->last, ', ', $cust_main->first, '</TD>'; @@ -105,8 +105,8 @@ print '</TR>', $cust_main->country, '</TD></TR>', ; - my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone'; - my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone'; + my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone'; + my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone'; print '<TR><TD ALIGN="right">'. $daytime_label. '</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', $cust_main->daytime || ' ', '</TD></TR>', @@ -166,7 +166,7 @@ print '</TD>'; print '<TD VALIGN="top">'; print &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2), - '<TR><TD ALIGN="right">Customer number</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Customer number</TD><TD BGCOLOR="#ffffff">', $custnum, '</TD></TR>', ; @@ -184,13 +184,13 @@ print '<TD VALIGN="top">'; my $referral = qsearchs('part_referral', { 'refnum' => $cust_main->refnum } ); - print '<TR><TD ALIGN="right">Advertising source</TD><TD BGCOLOR="#ffffff">', + print '<TR><TD ALIGN="right">Advertising source</TD><TD BGCOLOR="#ffffff">', $referral->refnum, ": ", $referral->referral, '</TD></TR>'; } print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">', $cust_main->otaker, '</TD></TR>'; - print '<TR><TD ALIGN="right">Referring Customer</TD><TD BGCOLOR="#ffffff">'; + print '<TR><TD ALIGN="right">Referring Customer</TD><TD BGCOLOR="#ffffff">'; my $referring_cust_main = ''; if ( $cust_main->referral_custnum && ( $referring_cust_main = @@ -220,22 +220,22 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { print "Billing information (", qq!<A HREF="!, popurl(2), qq!misc/bill.cgi?$custnum">!, "Bill now</A>)", &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2), - '<TR><TD ALIGN="right">Tax exempt</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Tax exempt</TD><TD BGCOLOR="#ffffff">', $cust_main->tax ? 'yes' : 'no', '</TD></TR>', - '<TR><TD ALIGN="right">Postal invoices</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Postal invoices</TD><TD BGCOLOR="#ffffff">', ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no', '</TD></TR>', - '<TR><TD ALIGN="right">Email invoices</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Email invoices</TD><TD BGCOLOR="#ffffff">', join(', ', grep { $_ ne 'POST' } @invoicing_list ) || 'no', '</TD></TR>', - '<TR><TD ALIGN="right">Billing type</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Billing type</TD><TD BGCOLOR="#ffffff">', ; if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) { my $payinfo = $cust_main->payinfo; $payinfo = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4)); - print 'Credit card ', + print 'Credit card ', ( $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' ), '</TD></TR>', '<TR><TD ALIGN="right">Card number</TD><TD BGCOLOR="#ffffff">', @@ -247,7 +247,7 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { ; } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') { my( $account, $aba ) = split('@', $cust_main->payinfo ); - print 'Electronic check', + print 'Electronic check ', ( $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' ), '</TD></TR>', '<TR><TD ALIGN="right">Account number</TD><TD BGCOLOR="#ffffff">', @@ -260,7 +260,7 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { } elsif ( $cust_main->payby eq 'LECB' ) { $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/; my $payinfo = "$1-$2-$3"; - print 'Phone bill billing</TD></TR>', + print 'Phone bill billing</TD></TR>', '<TR><TD ALIGN="right">Phone number</TD><TD BGCOLOR="#ffffff">', $payinfo, '</TD></TR>', ; @@ -276,7 +276,7 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { ; } elsif ( $cust_main->payby eq 'COMP' ) { print 'Complimentary</TD></TR>', - '<TR><TD ALIGN="right">Authorized by</TD><TD BGCOLOR="#ffffff">', + '<TR><TD ALIGN="right">Authorized by</TD><TD BGCOLOR="#ffffff">', $cust_main->payinfo, '</TD></TR>', '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">', $cust_main->paydate, '</TD></TR>', @@ -370,7 +370,7 @@ print qq!<BR><A NAME="cust_pkg">Packages</A> !, #get package info -my $packages = get_packages($cust_main); +my $packages = get_packages($cust_main, $conf); if ( @$packages ) { %> @@ -401,27 +401,30 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { <%=$pkg->{pkg}%> - <%=$pkg->{comment}%> ( <%=pkg_details_link($pkg)%> )<BR> <% unless ($pkg->{cancel}) { %> ( <%=pkg_change_link($pkg)%> ) - ( <%=pkg_dates_link($pkg)%> | <%=pkg_customize_link($pkg)%> ) + ( <%=pkg_dates_link($pkg)%> | <%=pkg_customize_link($pkg,$custnum)%> ) <% } %> </TD> <% #foreach (qw(setup last_bill next_bill susp expire cancel)) { - # print qq! <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_) . qq!</TD>\n!; + # print qq! <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_,$conf) . qq!</TD>\n!; #} print "<TD ROWSPAN=$rowspan>". &itable(''); - #move - my %freq = ( - 1 => 'monthly', - 2 => 'bi-monthly', - 3 => 'quarterly', - 6 => 'semi-annually', - 12 => 'annually', - 24 => 'bi-annually', - 36 => 'tri-annually', - ); - sub freq { + + #false laziness w/edit/part_pkg.cgi + my %freq = ( #move this + '1d' => 'daily', + '1w' => 'weekly', + '2w' => 'biweekly (every 2 weeks)', + '1' => 'monthly', + '2' => 'bimonthly (every 2 months)', + '3' => 'quarterly (every 3 months)', + '6' => 'semiannually (every 6 months)', + '12' => 'annually', + '24' => 'biannually (every 2 years)', + ); + my $freq = shift; exists $freq{$freq} ? $freq{$freq} : "every $freq months"; } @@ -431,17 +434,17 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { if ( $pkg->{cancel} ) { #status: cancelled print '<TR><TD><FONT COLOR="#ff0000"><B>Cancelled </B></FONT></TD>'. - '<TD>'. pkg_datestr($pkg,'cancel'). '</TD></TR>'; + '<TD>'. pkg_datestr($pkg,'cancel',$conf). '</TD></TR>'; unless ( $pkg->{setup} ) { print '<TR><TD COLSPAN=2>Never billed</TD></TR>'; } else { print "<TR><TD>Setup </TD><TD>". - pkg_datestr($pkg, 'setup'). '</TD></TR>'; + pkg_datestr($pkg, 'setup',$conf). '</TD></TR>'; print "<TR><TD>Last bill </TD><TD>". - pkg_datestr($pkg, 'last_bill'). '</TD></TR>' + pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>' if $pkg->{'last_bill'}; print "<TR><TD>Suspended </TD><TD>". - pkg_datestr($pkg, 'susp'). '</TD></TR>' + pkg_datestr($pkg, 'susp',$conf). '</TD></TR>' if $pkg->{'susp'}; } @@ -449,19 +452,19 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { if ( $pkg->{susp} ) { #status: suspended print '<TR><TD><FONT COLOR="#FF9900"><B>Suspended</B> </FONT></TD>'. - '<TD>'. pkg_datestr($pkg,'susp'). '</TD></TR>'; + '<TD>'. pkg_datestr($pkg,'susp',$conf). '</TD></TR>'; unless ( $pkg->{setup} ) { print '<TR><TD COLSPAN=2>Never billed</TD></TR>'; } else { print "<TR><TD>Setup </TD><TD>". - pkg_datestr($pkg, 'setup'). '</TD></TR>'; + pkg_datestr($pkg, 'setup',$conf). '</TD></TR>'; } print "<TR><TD>Last bill </TD><TD>". - pkg_datestr($pkg, 'last_bill'). '</TD></TR>' + pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>' if $pkg->{'last_bill'}; # next bill ?? print "<TR><TD>Expires </TD><TD>". - pkg_datestr($pkg, 'expire'). '</TD></TR>' + pkg_datestr($pkg, 'expire',$conf). '</TD></TR>' if $pkg->{'expire'}; print '<TR><TD COLSPAN=2>( '. pkg_unsuspend_link($pkg). ' | '. pkg_cancel_link($pkg). ' )</TD></TR>'; @@ -484,24 +487,24 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { unless ( $pkg->{freq} ) { print "<TR><TD COLSPAN=2>One-time charge</TD></TR>". '<TR><TD>Billed </TD><TD>'. - pkg_datestr($pkg,'setup'). '</TD></TR>'; + pkg_datestr($pkg,'setup',$conf). '</TD></TR>'; } else { print '<TR><TD COLSPAN=2><FONT COLOR="#00CC00"><B>Active</B></FONT>'. ', billed '. freq($pkg->{freq}). '</TD></TR>'. '<TR><TD>Setup </TD><TD>'. - pkg_datestr($pkg, 'setup'). '</TD></TR>'; + pkg_datestr($pkg, 'setup',$conf). '</TD></TR>'; } } print "<TR><TD>Last bill </TD><TD>". - pkg_datestr($pkg, 'last_bill'). '</TD></TR>' + pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>' if $pkg->{'last_bill'}; print "<TR><TD>Next bill </TD><TD>". - pkg_datestr($pkg, 'next_bill'). '</TD></TR>' + pkg_datestr($pkg, 'next_bill',$conf). '</TD></TR>' if $pkg->{'next_bill'}; print "<TR><TD>Expires </TD><TD>". - pkg_datestr($pkg, 'expire'). '</TD></TR>' + pkg_datestr($pkg, 'expire',$conf). '</TD></TR>' if $pkg->{'expire'}; if ( $pkg->{freq} ) { print '<TR><TD COLSPAN=2>( '. pkg_suspend_link($pkg). @@ -551,6 +554,11 @@ function cust_pay_unapply_areyousure(href) { == true) window.location.href = href; } +function cust_credit_areyousure(href) { + if (confirm("Are you sure you want to delete this credit?") + == true) + window.location.href = href; +} </SCRIPT> END @@ -625,9 +633,13 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { $cust_credit->reason, time2str("%D", $cust_credit_bill->_date), ); + my $delete = + $cust_credit->closed !~ /^Y/i && $conf->exists('deletecredits') + ? qq! (<A HREF="javascript:cust_credit_areyousure('${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum. qq!')">delete</A>)! + : ''; push @history, "$date\tCredit #$crednum: $reason<BR>". - "(applied to invoice #$invnum on $app_date)\t\t\t$amount\t"; + "(applied to invoice #$invnum on $app_date)$delete\t\t\t$amount\t"; } } @@ -656,12 +668,16 @@ if ( $conf->config('payby-default') ne 'HIDE' ) { qsearch('cust_credit',{'custnum'=>$custnum}); foreach my $credit (@credits) { my($cref)=$credit->hashref; + my $delete = + $credit->closed !~ /^Y/i && $conf->exists('deletecredits') + ? qq! (<A HREF="javascript:cust_credit_areyousure('${p}misc/delete-cust_credit.cgi?!. $credit->crednum. qq!')">delete</A>)! + : ''; push @history, $cref->{_date} . "\t" . qq!<A HREF="! . popurl(2). qq!edit/cust_credit_bill.cgi?!. $cref->{crednum} . qq!">!. '<b><font size="+1" color="#ff0000">Unapplied credit #' . $cref->{crednum} . "</font></b></A>: ". - $cref->{reason} . "\t\t\t" . $credit->credited . "\t"; + $cref->{reason} . "$delete\t\t\t" . $credit->credited . "\t"; } my(@refunds)=qsearch('cust_refund',{'custnum'=> $custnum } ); @@ -767,52 +783,79 @@ sub keyfield_numerically { (split(/\t/,$a))[0] <=> (split(/\t/,$b))[0]; } sub get_packages { + my $cust_main = shift or return undef; + my $conf = shift; + + my @packages = (); + + foreach my $cust_pkg ( + $conf->exists('hidecancelledpackages') + ? $cust_main->ncancelled_pkgs + : $cust_main->all_pkgs + ) { + + my $part_pkg = $cust_pkg->part_pkg; + + my %pkg = (); + $pkg{pkgnum} = $cust_pkg->pkgnum; + $pkg{pkg} = $part_pkg->pkg; + $pkg{pkgpart} = $part_pkg->pkgpart; + $pkg{comment} = $part_pkg->getfield('comment'); + $pkg{freq} = $part_pkg->freq; + $pkg{setup} = $cust_pkg->getfield('setup'); + $pkg{last_bill} = $cust_pkg->getfield('last_bill'); + $pkg{next_bill} = $cust_pkg->getfield('bill'); + $pkg{susp} = $cust_pkg->getfield('susp'); + $pkg{expire} = $cust_pkg->getfield('expire'); + $pkg{cancel} = $cust_pkg->getfield('cancel'); + + my %svcparts = (); -my $cust_main = shift or return undef; - -my @packages = (); - -foreach my $cust_pkg (($conf->exists('hidecancelledpackages') ? ($cust_main->ncancelled_pkgs) - : ($cust_main->all_pkgs))) { - - my $part_pkg = $cust_pkg->part_pkg; - - my %pkg = (); - $pkg{pkgnum} = $cust_pkg->pkgnum; - $pkg{pkg} = $part_pkg->pkg; - $pkg{pkgpart} = $part_pkg->pkgpart; - $pkg{comment} = $part_pkg->getfield('comment'); - $pkg{freq} = $part_pkg->freq; - $pkg{setup} = $cust_pkg->getfield('setup'); - $pkg{last_bill} = $cust_pkg->getfield('last_bill'); - $pkg{next_bill} = $cust_pkg->getfield('bill'); - $pkg{susp} = $cust_pkg->getfield('susp'); - $pkg{expire} = $cust_pkg->getfield('expire'); - $pkg{cancel} = $cust_pkg->getfield('cancel'); - - $pkg{svcparts} = []; - - foreach my $pkg_svc (qsearch('pkg_svc', { 'pkgpart' => $part_pkg->pkgpart })) { - - next if ($pkg_svc->quantity == 0); - - my $part_svc = qsearchs('part_svc', { 'svcpart' => $pkg_svc->svcpart }); - - my $svcpart = {}; - $svcpart->{svcpart} = $part_svc->svcpart; - $svcpart->{svc} = $part_svc->svc; - $svcpart->{svcdb} = $part_svc->svcdb; - $svcpart->{quantity} = $pkg_svc->quantity; - $svcpart->{count} = 0; + foreach my $pkg_svc ( + qsearch('pkg_svc', { 'pkgpart' => $part_pkg->pkgpart }) + ) { + + next if ($pkg_svc->quantity == 0); + + my $part_svc = qsearchs('part_svc', { 'svcpart' => $pkg_svc->svcpart }); + + my $svcpart = {}; + $svcpart->{svcpart} = $part_svc->svcpart; + $svcpart->{svc} = $part_svc->svc; + $svcpart->{svcdb} = $part_svc->svcdb; + $svcpart->{quantity} = $pkg_svc->quantity; + $svcpart->{count} = 0; + + $svcpart->{services} = []; - $svcpart->{services} = []; + $svcparts{$svcpart->{svcpart}} = $svcpart; - foreach my $cust_svc (qsearch('cust_svc', { 'pkgnum' => $cust_pkg->pkgnum, - 'svcpart' => $part_svc->svcpart } )) { + } - my $svc = {}; - $svc->{svcnum} = $cust_svc->svcnum; - $svc->{label} = ($cust_svc->label)[1]; + foreach my $cust_svc ( + qsearch( 'cust_svc', { + 'pkgnum' => $cust_pkg->pkgnum, + #'svcpart' => $part_svc->svcpart, + } + ) + ) { + + warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n"; + my $svc = { + 'svcnum' => $cust_svc->svcnum, + 'label' => ($cust_svc->label)[1], + }; + + #false laziness with above, to catch extraneous services. whole + #damn thing should be OO... + my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= { + 'svcpart' => $cust_svc->svcpart, + 'svc' => $cust_svc->part_svc->svc, + 'svcdb' => $cust_svc->part_svc->svcdb, + 'quantity' => 0, + 'count' => 0, + 'services' => [], + } ); push @{$svcpart->{services}}, $svc; @@ -820,29 +863,27 @@ foreach my $cust_pkg (($conf->exists('hidecancelledpackages') ? ($cust_main->nca } - push @{$pkg{svcparts}}, $svcpart; + $pkg{svcparts} = [ values %svcparts ]; + push @packages, \%pkg; + } - - push @packages, \%pkg; - -} - -return \@packages; + + return \@packages; } sub svc_link { - my ($svcpart, $svc) = (shift,shift) or return ''; - return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!; + my ($svcpart, $svc) = (shift,shift) or return ''; + return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!; } sub svc_label_link { - my ($svcpart, $svc) = (shift,shift) or return ''; - return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!; + my ($svcpart, $svc) = (shift,shift) or return ''; + return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!; } @@ -875,7 +916,7 @@ sub pkgsort_pkgnum_cancel { } sub pkg_datestr { - my($pkg, $field) = @_ or return ''; + my($pkg, $field, $conf) = @_ or return ''; return ' ' unless $pkg->{$field}; my $format = $conf->exists('pkg_showtimes') ? '<B>%D</B> <FONT SIZE=-3>%l:%M:%S%P %z</FONT>' @@ -916,6 +957,7 @@ sub pkg_dates_link { sub pkg_customize_link { my $pkg = shift or return ''; + my $custnum = shift; return qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};pkgnum=$pkg->{pkgnum}">Customize</A>!; } diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 4e01db341..58591fcb5 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -57,7 +57,9 @@ function areyousure(href) { <% #if ( $cust_pkg && $cust_pkg->part_pkg->plan eq 'sqlradacct_hour' ) { -if ( $part_svc->part_export('sqlradius') ) { +if ( $part_svc->part_export('sqlradius') + || $part_svc->part_export('sqlradius_withdomain') +) { my $last_bill; my %plandata; @@ -74,9 +76,9 @@ if ( $part_svc->part_export('sqlradius') ) { } my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time ); - my $h = int($seconds/3600); - my $m = int( ($seconds%3600) / 60 ); - my $s = $seconds%60; + 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' @@ -86,7 +88,7 @@ if ( $part_svc->part_export('sqlradius') ) { ) / 1048576; if ( $seconds ) { - print "Online <B>$h</B>h <B>$m</B>m <B>$s</B>s"; + print "Online <B>$hour</B>h <B>$min</B>m <B>$sec</B>s"; } else { print 'Has not logged on'; } diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 677a4b058..a4ec7569e 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -37,10 +37,9 @@ my ( $svc_broadband->getfield('speed_up'), $svc_broadband->getfield('ip_addr') ); +%> - - -print header('Broadband Service View', menubar( +<%=header('Broadband Service View', menubar( ( ( $custnum ) ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum", "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", @@ -49,29 +48,96 @@ print header('Broadband Service View', menubar( "${p}misc/cancel-unaudited.cgi?$svcnum" ) ), "Main menu" => $p, -)). - qq!<A HREF="${p}edit/svc_broadband.cgi?$svcnum">Edit this information</A><BR>!. - ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2). - qq!<TR><TD ALIGN="right">Service number</TD>!. - qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. - qq!<TR><TD ALIGN="right">Router</TD>!. - qq!<TD BGCOLOR="#ffffff">$routernum: $routername</TD></TR>!. - qq!<TR><TD ALIGN="right">Download Speed</TD>!. - qq!<TD BGCOLOR="#ffffff">$speed_down</TD></TR>!. - qq!<TR><TD ALIGN="right">Upload Speed</TD>!. - qq!<TD BGCOLOR="#ffffff">$speed_up</TD></TR>!. - qq!<TR><TD ALIGN="right">IP Address</TD>!. - qq!<TD BGCOLOR="#ffffff">$ip_addr</TD></TR>!. - '</TD></TR><TR ROWSPAN="1"><TD></TD></TR>'; +)) +%> +<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">Router</TD> + <TD BGCOLOR="#ffffff"><%=$routernum%>: <%=$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> + <TR> + <TD ALIGN="right">IP Address</TD> + <TD BGCOLOR="#ffffff"><%=$ip_addr%></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"; + print $svc_broadband->pvf($_)->widget('HTML', 'view', + $svc_broadband->getfield($_)), "\n"; } -print '</TABLE>'; +%> + </TABLE> + </TD> + </TR> +</TABLE> -print '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). - '</BODY></HTML>' -; +<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 @addr_block; + if (@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 (@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)%> + </BODY> +</HTML> + diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi new file mode 100644 index 000000000..e5c977f13 --- /dev/null +++ b/httemplate/view/svc_external.cgi @@ -0,0 +1,52 @@ +<!-- mason kludge --> +<% + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_external = qsearchs( 'svc_external', { 'svcnum' => $svcnum } ) + or die "svc_external: 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); +if ($pkgnum) { + $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); + $custnum = $cust_pkg->custnum; +} else { + $cust_pkg = ''; + $custnum = ''; +} +#eofalse + +%> + +<%= header('External Service View', menubar( + ( ( $custnum ) + ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum", + "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + ) + : ( "Cancel this (unaudited) external service" => + "${p}misc/cancel-unaudited.cgi?$svcnum" ) + ), + "Main menu" => $p, +)) %> + +<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">External ID</TD> + <TD BGCOLOR="#ffffff"><%= $svc_external->id %></TD></TR> +<TR><TD ALIGN="right">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) %> +</BODY></HTML> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi index 5d619de54..650972429 100755 --- a/httemplate/view/svc_forward.cgi +++ b/httemplate/view/svc_forward.cgi @@ -28,7 +28,7 @@ print header('Mail Forward View', menubar( ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum", "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", ) - : ( "Cancel this (unaudited) account" => + : ( "Cancel this (unaudited) mail forward" => "${p}misc/cancel-unaudited.cgi?$svcnum" ) ), "Main menu" => $p, @@ -39,16 +39,25 @@ my($srcsvc,$dstsvc,$dst) = ( $svc_forward->dstsvc, $svc_forward->dst, ); +my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; + my $svc = $part_svc->svc; -my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc}) - or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc"; -my $source = $svc_acct->email; + +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{ +} else { $destination = $dst; } |