diff options
Diffstat (limited to 'httemplate')
358 files changed, 31731 insertions, 0 deletions
diff --git a/httemplate/.htaccess b/httemplate/.htaccess new file mode 100755 index 000000000..f8c6b9c0c --- /dev/null +++ b/httemplate/.htaccess @@ -0,0 +1,3 @@ +AuthName Freeside +AuthType Basic +require valid-user diff --git a/httemplate/autohandler b/httemplate/autohandler new file mode 100644 index 000000000..a017eccb7 --- /dev/null +++ b/httemplate/autohandler @@ -0,0 +1,32 @@ +% $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' ) { + + ## barely worth it, just in case someone tries to use profiling on a + ## non-RT install + #eval "use Text::Wrapper;"; + #die $@ if $@; + + my $wrapper = new Text::Wrapper( columns => 80 ); + my $text = dbh->sprintProfile(); + #my $text = $wrapper->wrap( dbh->sprintProfile() ); + $text =~ s/^/ /mg; + + $profile = '<PRE>'. + encode_entities( $text ). + #"\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/access_group.html b/httemplate/browse/access_group.html new file mode 100644 index 000000000..d1c3aea4c --- /dev/null +++ b/httemplate/browse/access_group.html @@ -0,0 +1,79 @@ +% +% +%my $html_init = +% "Internal access groups control access to the back-office interface.<BR><BR>". +% qq!<A HREF="${p}edit/access_group.html"><I>Add an internal access group</I></A><BR><BR>!; +% +%#false laziness w/access_user.html & agent_type.cgi +%my $agents_sub = sub { +% my $access_group = shift; +% +% [ map { +% my $access_groupagent = $_; +% my $agent = $access_groupagent->agent; +% [ +% { +% 'data' => $agent->agent, +% 'align' => 'left', +% 'link' => $p. 'edit/agent.cgi?'. $agent->agentnum, +% }, +% ]; +% } +% grep { $_->agent } #? +% $access_group->access_groupagent, +% +% ]; +% +%}; +% +%my $rights_sub = sub { +% my $access_group = shift; +% +% [ map { my $access_right = $_; +% [ +% { +% 'data' => $access_right->rightname, +% 'align' => 'left', +% }, +% ]; +% } +% $access_group->access_rights, +% +% ]; +% +%}; +% +%my $count_query = 'SELECT COUNT(*) FROM access_group'; +% +%my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; +% +% +<% include( 'elements/browse.html', + 'title' => 'Internal Access Groups', + 'menubar' => [ # 'Main menu' => $p, + 'Internal users' => $p.'browse/access_user.html', + ], + 'html_init' => $html_init, + 'name' => 'internal access groups', + 'query' => { 'table' => 'access_group', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY groupname', #?? + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Group name', + 'Agents', + 'Rights', + ], + 'fields' => [ 'groupnum', + 'groupname', + $agents_sub, + $rights_sub, + ], + 'links' => [ $link, + $link, + '', + '', + ], + ) +%> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html new file mode 100644 index 000000000..05384289a --- /dev/null +++ b/httemplate/browse/access_user.html @@ -0,0 +1,99 @@ +% +% +%my $html_init = +% "Internal users have access to the back-office interface. Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees. It is <B>highly recommended</B> to add a <B>separate account for each person</B> rather than using role accounts.<BR><BR>". +% qq!<A HREF="${p}edit/access_user.html"><I>Add an internal user</I></A><BR><BR>!; +% +%#false laziness w/part_pkg.cgi +%my %search = (); +%my $search = ''; +%unless ( $cgi->param('showdisabled') ) { +% %search = ( 'disabled' => '' ); +% $search = "( disabled = '' OR disabled IS NULL )"; +%} +% +%#false laziness w/access_group.html & agent_type.cgi +%my $groups_sub = sub { +% my $access_user = shift; +% +% [ map { +% my $access_usergroup = $_; +% my $access_group = $access_usergroup->access_group; +% [ +% { +% 'data' => $access_group->groupname, +% 'align' => 'left', +% 'link' => +% $p. 'edit/access_group.html?'. $access_usergroup->groupnum, +% }, +% ]; +% } +% grep { $_->access_group # and ! $_->access_group->disabled +% } +% $access_user->access_usergroup, +% +% ]; +% +%}; +% +%my $posttotal; +%if ( $cgi->param('showdisabled') ) { +% $cgi->param('showdisabled', 0); +% $posttotal = '( <a href="'. $cgi->self_url. '">hide disabled users</a> )'; +% $cgi->param('showdisabled', 1); +%} else { +% $cgi->param('showdisabled', 1); +% $posttotal = '( <a href="'. $cgi->self_url. '">show disabled users</a> )'; +% $cgi->param('showdisabled', 0); +%} +% +%my $count_query = 'SELECT COUNT(*) FROM access_user'; +%$count_query .= " WHERE $search" +% if $search; +% +%my $link = [ $p.'edit/access_user.html?', 'usernum' ]; +% +%my @header = ( '#', 'Username' ); +%my @fields = ( 'usernum', 'username' ); +%my $align = 'rl'; +%my @links = ( $link, $link ); +%my @style = ( '', '' ); +% +%#false laziness w/part_pkg.cgi +%#unless ( $cgi->param('showdisabled') ) { #its been reversed already +%if ( $cgi->param('showdisabled') ) { #its been reversed already +% push @header, 'Status'; +% push @fields, sub { shift->disabled +% ? '<FONT COLOR="#FF0000">DISABLED</FONT>' +% : '<FONT COLOR="#00CC00">Active</FONT>' +% }; +% push @links, ''; +% $align .= 'c'; +% push @style, 'b'; +%} +% +%push @header, 'Full name', 'Groups'; +%push @fields, 'name', $groups_sub; +%push @links, $link, ''; +%$align .= 'll'; +% +% +<% include( 'elements/browse.html', + 'title' => 'Internal Users', + 'menubar' => [ #'Main menu' => $p, + 'Internal access groups' => $p.'browse/access_group.html', + ], + 'html_init' => $html_init, + 'html_posttotal' => $posttotal, + 'name' => 'internal users', + 'query' => { 'table' => 'access_user', + 'hashref' => \%search, + 'extra_sql' => 'ORDER BY last, first', + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'style' => \@style, + ) +%> diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi new file mode 100644 index 000000000..87f46c0e7 --- /dev/null +++ b/httemplate/browse/addr_block.cgi @@ -0,0 +1,83 @@ +<% include("/elements/header.html",'Address Blocks', menubar('Main Menu' => $p)) %> +% +% +%use NetAddr::IP; +% +%my @addr_block = qsearch('addr_block', {}); +%my @router = qsearch('router', {}); +%my $block; +%my $p2 = popurl(2); +%my $path = $p2 . "edit/process/addr_block"; +% +% +% if ($cgi->param('error')) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> + <BR><BR> +% } + + +<%table()%> +% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { + + <TR> + <TD><%$block->NetAddr%></TD> +% if (my $router = $block->router) { +% if (scalar($block->svc_broadband) == 0) { + + <TD> + <%$router->routername%> + </TD> + <TD> + <FORM ACTION="<%$path%>/deallocate.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> + <INPUT TYPE="submit" NAME="submit" VALUE="Deallocate"> + </FORM> + </TD> +% } else { + + <TD COLSPAN="2"> + <%$router->routername%> + </TD> +% } +% } else { + + <TD> + <FORM ACTION="<%$path%>/allocate.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> + <SELECT NAME="routernum" SIZE="1"> +% foreach (@router) { + + <OPTION VALUE="<%$_->routernum %>"><%$_->routername%></OPTION> +% } + + </SELECT> + <INPUT TYPE="submit" NAME="submit" VALUE="Allocate"> + </FORM> + </TD> + <TD> + <FORM ACTION="<%$path%>/split.cgi" METHOD="POST"> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>"> + <INPUT TYPE="submit" NAME="submit" VALUE="Split"> + </FORM> + </TD> + </TR> +% } +% } + + <TR><TD COLSPAN="3"><BR></TD></TR> + <TR> + <FORM ACTION="<%$path%>/add.cgi" METHOD="POST"> + <TD>Gateway/Netmask</TD> + <TD> + <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2"> + </TD> + <TD> + <INPUT TYPE="submit" NAME="submit" VALUE="Add"> + </TD> + </FORM> + </TR> +</TABLE> +</BODY> +</HTML> + diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi new file mode 100755 index 000000000..001e6ba50 --- /dev/null +++ b/httemplate/browse/agent.cgi @@ -0,0 +1,372 @@ +% +% +% my %search; +% if ( $cgi->param('showdisabled') +% || !dbdef->table('agent')->column('disabled') ) { +% %search = (); +% } else { +% %search = ( 'disabled' => '' ); +% } +% +% my $conf = new FS::Conf; +% +% + +<% include("/elements/header.html",'Agent Listing', menubar( + 'Main Menu' => $p, + 'Agent Types' => $p. 'browse/agent_type.cgi', +# 'Add new agent' => '../edit/agent.cgi' +)) %> +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> )'; } + %> +% } + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Customer<BR>packages</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Reports</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Registration codes</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Prepaid cards</TH> +% if ( $conf->config('ticket_system') ) { + + <TH CLASS="grid" BGCOLOR="#cccccc">Ticketing</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment Gateway Overrides</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Freq.</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Prog.</FONT></TH> +</TR> +% +%# <TH><FONT SIZE=-1>Agent #</FONT></TH> +%# <TH>Agent</TH> +% +%foreach my $agent ( sort { +% #$a->getfield('agentnum') <=> $b->getfield('agentnum') +% $a->getfield('agent') cmp $b->getfield('agent') +%} qsearch('agent', \%search ) ) { +% +% my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'. +% 'agentnum='. $agent->agentnum; +% +% my $cust_pkg_link = $p. 'search/cust_pkg.cgi?agentnum='. $agent->agentnum; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"> + <% $agent->agentnum %></A></TD> +% if ( dbdef->table('agent')->column('disabled') +% && !$cgi->param('showdisabled') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->disabled ? 'DISABLED' : '' %></TD> +% } + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>"> + <% $agent->agent %></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<%$p%>edit/agent_type.cgi?<% $agent->typenum %>"><% $agent->agent_type->atype %></A></TD> + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#7e0079"> + <% my $num_prospect = $agent->num_prospect_cust_main %> + </FONT> + </TH> + + <TD> +% if ( $num_prospect ) { + + <A HREF="<% $cust_main_link %>&prospect=1"> +% } +prospects +% if ($num_prospect ) { +</A> +% } + + <TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#0000CC"> + <% my $num_inactive = $agent->num_inactive_cust_main %> + </FONT> + </TH> + + <TD> +% if ( $num_inactive ) { + + <A HREF="<% $cust_main_link %>&inactive=1"> +% } +inactive +% if ( $num_inactive ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#00CC00"> + <% my $num_active = $agent->num_active_cust_main %> + </FONT> + </TH> + + <TD> +% if ( $num_active ) { + + <A HREF="<% $cust_main_link %>&active=1"> +% } +active +% if ( $num_active ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#FF9900"> + <% my $num_susp = $agent->num_susp_cust_main %> + </FONT> + </TH> + + <TD> +% if ( $num_susp ) { + + <A HREF="<% $cust_main_link %>&suspended=1"> +% } +suspended +% if ( $num_susp ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#FF0000"> + <% my $num_cancel = $agent->num_cancel_cust_main %> + </FONT> + </TH> + + <TD> +% if ( $num_cancel ) { + + <A HREF="<% $cust_main_link %>&showcancelledcustomers=1&cancelled=1"> +% } +cancelled +% if ( $num_cancel ) { +</A> +% } + + </TD> + </TR> + + </TABLE> + </TD> + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="bottom"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#0000CC"> + <% my $num_inactive_pkg = $agent->num_inactive_cust_pkg %> + </FONT> + </TH> + + <TD> +% if ( $num_inactive_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=inactive"> +% } +inactive +% if ( $num_inactive_pkg ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#00CC00"> + <% my $num_active_pkg = $agent->num_active_cust_pkg %> + </FONT> + </TH> + + <TD> +% if ( $num_active_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=active"> +% } +active +% if ( $num_active_pkg ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#FF9900"> + <% my $num_susp_pkg = $agent->num_susp_cust_pkg %> + </FONT> + + </TH> + <TD> +% if ( $num_susp_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=suspended"> +% } +suspended +% if ( $num_susp_pkg ) { +</A> +% } + + </TD> + </TR> + + <TR> + <TH ALIGN="right" WIDTH="40%"> + <FONT COLOR="#FF0000"> + <% my $num_cancel_pkg = $agent->num_cancel_cust_pkg %> + </FONT> + </TH> + + <TD> +% if ( $num_cancel_pkg ) { + + <A HREF="<% $cust_pkg_link %>&magic=cancelled"> +% } +cancelled +% if ( $num_cancel_pkg ) { +</A> +% } + + </TD> + </TR> + + </TABLE> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $p %>search/report_cust_pay.html?agentnum=<% $agent->agentnum %>">Payments</A> + <BR><A HREF="<% $p %>search/report_cust_credit.html?agentnum=<% $agent->agentnum %>">Credits</A> + <BR><A HREF="<% $p %>search/report_receivables.cgi?agentnum=<% $agent->agentnum %>">A/R Aging</A> + <!--<BR><A HREF="<% $p %>search/money_time.cgi?agentnum=<% $agent->agentnum %>">Sales/Credits/Receipts</A>--> + + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% my $num_reg_code = $agent->num_reg_code %> +% if ( $num_reg_code ) { + + <A HREF="<%$p%>search/reg_code.html?agentnum=<% $agent->agentnum %>"> +% } +Unused +% if ( $num_reg_code ) { +</A> +% } + + <BR><A HREF="<%$p%>edit/reg_code.cgi?agentnum=<% $agent->agentnum %>">Generate codes</A> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% my $num_prepay_credit = $agent->num_prepay_credit %> +% if ( $num_prepay_credit ) { + + <A HREF="<%$p%>search/prepay_credit.html?agentnum=<% $agent->agentnum %>"> +% } +Unused +% if ( $num_prepay_credit ) { +</A> +% } + + <BR><A HREF="<%$p%>edit/prepay_credit.cgi?agentnum=<% $agent->agentnum %>">Generate cards</A> + </TD> +% if ( $conf->config('ticket_system') ) { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $agent->ticketing_queueid ) { + + Queue: <% $agent->ticketing_queueid %>: <% $agent->ticketing_queue %><BR> +% } + + </TD> +% } + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <TABLE CELLSPACING=0 CELLPADDING=0> +% foreach my $override ( +% # sort { } want taxclass-full stuff first? and default cards (empty cardtype) +% qsearch('agent_payment_gateway', { 'agentnum' => $agent->agentnum } ) +% ) { +% + + <TR> + <TD> + <% $override->cardtype || 'Default' %> to <% $override->payment_gateway->gateway_module %> (<% $override->payment_gateway->gateway_username %>) + <% $override->taxclass + ? ' for '. $override->taxclass. ' only' + : '' + %> + <FONT SIZE=-1><A HREF="<%$p%>misc/delete-agent_payment_gateway.cgi?<% 'XXXoverridenum' %>">(delete)</A></FONT> + </TD> + </TR> +% } + + <TR> + <TD><FONT SIZE=-1><A HREF="<%$p%>edit/agent_payment_gateway.html?agentnum=<% $agent->agentnum %>">(add override)</A></FONT></TD> + </TR> + </TABLE> + </TD> + +<!-- + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->freq %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $agent->prog %></TD> +--> + + </TR> +% } + + + </TABLE> + </BODY> +</HTML> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi new file mode 100755 index 000000000..318d0b6ea --- /dev/null +++ b/httemplate/browse/agent_type.cgi @@ -0,0 +1,60 @@ +% +% +%my $html_init = +% 'Agent types define groups of packages that you can then assign to'. +% ' particular agents.<BR><BR>'. +% qq!<A HREF="${p}edit/agent_type.cgi"><I>Add a new agent type</I></A><BR><BR>!; +% +%my $count_query = 'SELECT COUNT(*) FROM agent_type'; +% +%#false laziness w/access_user.html +%my $packages_sub = sub { +% my $agent_type = shift; +% +% [ map { +% my $type_pkgs = $_; +% #my $part_pkg = $type_pkgs->part_pkg; +% [ +% { +% #'data' => $part_pkg->pkg. ' - '. $part_pkg->comment, +% 'data' => $type_pkgs->pkg. ' - '. $type_pkgs->comment, +% 'align' => 'left', +% 'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart, +% }, +% ]; +% } +% +% $agent_type->type_pkgs_enabled +% ]; +% +%}; +% +%my $link = [ $p.'edit/agent_type.cgi?', 'typenum' ]; +% +% +<% include( 'elements/browse.html', + 'title' => 'Agent Types', + 'menubar' => [ #'Main menu' => $p, + 'Agents' =>"${p}browse/agent.cgi", + ], + 'html_init' => $html_init, + 'name' => 'agent types', + 'query' => { 'table' => 'agent_type', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY typenum', # 'ORDER BY atype', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Agent Type', + 'Packages', + ], + 'fields' => [ 'typenum', + 'atype', + $packages_sub, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi new file mode 100755 index 000000000..69a7eb9a0 --- /dev/null +++ b/httemplate/browse/cust_main_county.cgi @@ -0,0 +1,166 @@ +<% include('/elements/header.html', "Tax Rate Listing", menubar( + 'Edit tax rates' => $p. "edit/cust_main_county.cgi", +)) %> + + Click on <u>expand country</u> to specify a country's tax rates by state. + <BR>Click on <u>expand state</u> to specify a state's tax rates by county. +% +%my $conf = new FS::Conf; +%my $enable_taxclasses = $conf->exists('enable_taxclasses'); +% +%if ( $enable_taxclasses ) { + + + <BR>Click on <u>expand taxclasses</u> to specify tax classes +% } + + +<BR><BR> +<% table() %> + + <TR> + <TH><FONT SIZE=-1>Country</FONT></TH> + <TH><FONT SIZE=-1>State</FONT></TH> + <TH>County</TH> + <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>Exemption</TH> + </TR> +% +%my @regions = sort { $a->country cmp $b->country +% or $a->state cmp $b->state +% or $a->county cmp $b->county +% or $a->taxclass cmp $b->taxclass +% } qsearch('cust_main_county',{}); +% +%my $sup=0; +%#foreach $cust_main_county ( @regions ) { +%for ( my $i=0; $i<@regions; $i++ ) { +% my $cust_main_county = $regions[$i]; +% my $hashref = $cust_main_county->hashref; +% +% + + <TR> + <TD BGCOLOR="#ffffff"><% $hashref->{country} %></TD> +% +% +% my $j; +% if ( $sup ) { +% $sup--; +% } else { +% +% #lookahead +% for ( $j=1; $i+$j<@regions; $j++ ) { +% 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->{setuptax} ne $regions[$i+$j]->setuptax +% || $hashref->{recurtax} ne $regions[$i+$j]->recurtax; +% } +% +% my $newsup=0; +% if ( $j>1 && $i+$j+1 < @regions +% && ( $hashref->{state} ne $regions[$i+$j+1]->state +% || $hashref->{country} ne $regions[$i+$j+1]->country +% ) +% && ( ! $i +% || $hashref->{state} ne $regions[$i-1]->state +% || $hashref->{country} ne $regions[$i-1]->country +% ) +% ) { +% $sup = $j-1; +% } else { +% $j = 1; +% } +% +% + + + <TD ROWSPAN=<% $j %><% + $hashref->{state} + ? ' BGCOLOR="#ffffff">'. $hashref->{state} + : qq! BGCOLOR="#cccccc">(ALL) <FONT SIZE=-1>!. + qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}. + qq!">expand country</A></FONT>! + %> +% if ( $j>1 ) { + + <FONT SIZE=-1><A HREF="<% $p %>edit/process/cust_main_county-collapse.cgi?<% $hashref->{taxnum} %>">collapse state</A></FONT> +% } + + + </TD> +% } +% # $sup=$newsup; + + + <TD +% if ( $hashref->{county} ) { +% + BGCOLOR="#ffffff"><% $hashref->{county} %> +% } else { +% + BGCOLOR="#cccccc">(ALL) +% if ( $hashref->{state} ) { + + <FONT SIZE=-1><A HREF="<% $p %>edit/cust_main_county-expand.cgi?<% $hashref->{taxnum} %>">expand state</A></FONT> +% } +% } + + </TD> + + <TD +% if ( $hashref->{taxclass} ) { +% + BGCOLOR="#ffffff"><% $hashref->{taxclass} %> +% } else { +% + BGCOLOR="#cccccc">(ALL) +% if ( $enable_taxclasses ) { + + <FONT SIZE=-1><A HREF="<% $p %>edit/cust_main_county-expand.cgi?taxclass<% $hashref->{taxnum} %>">expand taxclasses</A></FONT> +% } +% } + + </TD> + + <TD +% if ( $hashref->{taxname} ) { +% + BGCOLOR="#ffffff"><% $hashref->{taxname} %> +% } else { +% + BGCOLOR="#cccccc">Tax +% } + + </TD> + + <TD BGCOLOR="#ffffff"><% $hashref->{tax} %>%</TD> + + <TD BGCOLOR="#ffffff"> +% if ( $hashref->{exempt_amount} > 0 ) { + + $<% sprintf("%.2f", $hashref->{exempt_amount} ) %> per month<BR> +% } +% if ( $hashref->{setuptax} =~ /^Y$/i ) { + + Setup fee<BR> +% } +% if ( $hashref->{recurtax} =~ /^Y$/i ) { + + Recurring fee<BR> +% } + + + </TD> + + </TR> +% } + + +</TABLE> + +<% include('/elements/footer.html') %> diff --git a/httemplate/browse/elements/browse.html b/httemplate/browse/elements/browse.html new file mode 100644 index 000000000..2cc5a9660 --- /dev/null +++ b/httemplate/browse/elements/browse.html @@ -0,0 +1,6 @@ +<% include( '/search/elements/search.html', + @_, + 'disable_download' => 1, + 'disable_nonefound' => 1, + ) +%> diff --git a/httemplate/browse/inventory_class.html b/httemplate/browse/inventory_class.html new file mode 100644 index 000000000..6d9424e14 --- /dev/null +++ b/httemplate/browse/inventory_class.html @@ -0,0 +1,90 @@ +% +% +%tie my %labels, 'Tie::IxHash', +% 'num_avail' => 'Available', # <FONT SIZE="-1"><A HREF="eventually">(upload batch)</A></FONT>', +% 'num_used' => 'In use', #'Used', #'Allocated', +% 'num_total' => 'Total', +%; +% +%my %link = ( +% 'num_avail' => ';avail=1', +% 'num_used' => ';used=1', +% 'num_total' => '', +%); +% +%my %inv_action_link = ( +% 'num_avail' => [ 'upload batch', +% $p.'misc/inventory_item-import.html?classnum=', +% 'classnum' +% ], +%); +% +%my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; +% +% +<% include( 'elements/browse.html', + 'title' => 'Inventory Classes', + 'name' => 'inventory classes', + 'menubar' => [ 'Add a new inventory class' => + $p.'edit/inventory_class.html', + ], + 'query' => { 'table' => 'inventory_class', }, + 'count_query' => 'SELECT COUNT(*) FROM inventory_class', + 'header' => [ '#', 'Inventory class', 'Inventory' ], + 'fields' => [ 'classnum', + 'classname', + sub { + #my $inventory_class = shift; + my $i_c = shift; + + my $link = + $p. 'search/inventory_item.html?'. + 'classnum='. $i_c->classnum; + + my %actioncol = (); + foreach ( keys %inv_action_link ) { + my($label, $baseurl, $method) = + @{ $inv_action_link{$_} }; + my $url = $baseurl. $i_c->$method(); + $actioncol{$_} = + '<FONT SIZE="-1">'. + '('. + '<A HREF="'.$url.'">'. + $label. + '</A>'. + ')'. + '</FONT>'; + } + + my %num = map { + $_ => $i_c->$_(); + } keys %labels; + + [ map { + [ + { + 'data' => '<B>'. $num{$_}. '</B>', + 'align' => 'right', + }, + { + 'data' => $labels{$_}, + 'align' => 'left', + 'link' => ( $num{$_} + ? $link.$link{$_} + : '' + ), + }, + { 'data' => $actioncol{$_}, + 'align' => 'left', + }, + ] + } keys %labels + ]; + }, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi new file mode 100755 index 000000000..35ea06957 --- /dev/null +++ b/httemplate/browse/msgcat.cgi @@ -0,0 +1,42 @@ +<% include('/elements/header.html', "View Message catalog", menubar( + 'Edit message catalog' => $p. "edit/msgcat.cgi", +)) %> +% +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => 'en_US', +% 'options' => { 'en_US'=>'en_US' }, +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = "<BR>Messages for locale $layer<BR>". table(). +% "<TR><TH COLSPAN=2>Code</TH>". +% "<TH>Message</TH>"; +% $html .= "<TH>en_US Message</TH>" unless $layer eq 'en_US'; +% $html .= '</TR>'; +% +% #foreach my $msgcat ( sort { $a->msgcode cmp $b->msgcode } +% # qsearch('msgcat', { 'locale' => $layer } ) ) { +% foreach my $msgcat ( qsearch('msgcat', { 'locale' => $layer } ) ) { +% $html .= '<TR><TD>'. $msgcat->msgnum. '</TD>'. +% '<TD>'. $msgcat->msgcode. '</TD>'. +% '<TD>'. $msgcat->msg. '</TD>'; +% unless ( $layer eq 'en_US' ) { +% my $en_msgcat = qsearchs('msgcat', { +% 'locale' => 'en_US', +% 'msgcode' => $msgcat->msgcode, +% } ); +% $html .= '<TD>'. $en_msgcat->msg. '</TD>'; +% } +% $html .= '</TR>'; +% } +% +% $html .= '</TABLE>'; +% $html; +% }, +% +%); +% + + +<% $widget->html %> +<% include('/elements/footer.html') %> diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi new file mode 100755 index 000000000..022c65ea7 --- /dev/null +++ b/httemplate/browse/nas.cgi @@ -0,0 +1,81 @@ +<!-- mason kludge --> +% +% +%print header('NAS ports', menubar( +% 'Main Menu' => $p, +%)); +% +%my $now = time; +% +%foreach my $nas ( sort { $a->nasnum <=> $b->nasnum } qsearch( 'nas', {} ) ) { +% print $nas->nasnum. ": ". $nas->nas. " ". +% $nas->nasfqdn. " (". $nas->nasip. ") ". +% "as of ". time2str("%c",$nas->last). +% " (". &pretty_interval($now - $nas->last). " ago)<br>". +% &table(). "<TR><TH>Nas<BR>Port #</TH><TH>Global<BR>Port #</BR></TH>". +% "<TH>IP address</TH><TH>User</TH><TH>Since</TH><TH>Duration</TH><TR>", +% ; +% foreach my $port ( sort { +% $a->nasport <=> $b->nasport || $a->portnum <=> $b->portnum +% } qsearch( 'port', { 'nasnum' => $nas->nasnum } ) ) { +% my $session = $port->session; +% my($user, $since, $pretty_since, $duration); +% if ( ! $session ) { +% $user = "(empty)"; +% $since = 0; +% $pretty_since = "(never)"; +% $duration = ''; +% } elsif ( $session->logout ) { +% $user = "(empty)"; +% $since = $session->logout; +% } else { +% my $svc_acct = $session->svc_acct; +% $user = "<A HREF=\"$p/view/svc_acct.cgi?". $svc_acct->svcnum. "\">". +% $svc_acct->username. "</A>"; +% $since = $session->login; +% } +% $pretty_since = time2str("%c", $since) if $since; +% $duration = pretty_interval( $now - $since ). " ago" +% unless defined($duration); +% print "<TR><TD>". $port->nasport. "</TD><TD>". $port->portnum. "</TD><TD>". +% $port->ip. "</TD><TD>$user</TD><TD>$pretty_since". +% "</TD><TD>$duration</TD></TR>" +% ; +% } +% print "</TABLE><BR>"; +%} +% +%#Time::Duration?? +%sub pretty_interval { +% my $interval = shift; +% my %howlong = ( +% '604800' => 'week', +% '86400' => 'day', +% '3600' => 'hour', +% '60' => 'minute', +% '1' => 'second', +% ); +% +% my $pretty = ""; +% foreach my $key ( sort { $b <=> $a } keys %howlong ) { +% my $value = int( $interval / $key ); +% if ( $value ) { +% if ( $value == 1 ) { +% $pretty .= +% ( $howlong{$key} eq 'hour' ? 'an ' : 'a ' ). $howlong{$key}. " " +% } else { +% $pretty .= $value. ' '. $howlong{$key}. 's '; +% } +% } +% $interval -= $value * $key; +% } +% $pretty =~ /^\s*(\S.*\S)\s*$/; +% $1; +%} +% +%#print &table(), <<END; +%#<TR> +%# <TH>#</TH> +%# <TH>NAS</ +% + diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi new file mode 100755 index 000000000..2486c6669 --- /dev/null +++ b/httemplate/browse/part_bill_event.cgi @@ -0,0 +1,116 @@ +% +%my %search; +%if ( $cgi->param('showdisabled') ) { +% %search = (); +%} else { +% %search = ( 'disabled' => '' ); +%} +% +%my @part_bill_event = qsearch('part_bill_event', \%search ); +%my $total = scalar(@part_bill_event); +% + + +<% include("/elements/header.html",'Invoice Event Listing', menubar( 'Main Menu' => $p) ) %> + + Invoice events are actions taken on open invoices.<BR><BR> + +<A HREF="<% $p %>edit/part_bill_event.cgi"><I>Add a new invoice event</I></A> +<BR><BR> + +<% $total %> events +<% $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled events</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled events</a> )'; } +%> +<BR><BR> +% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname; +% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly'; +% foreach my $payby ( keys %payby ) { +% my $oldfreq = ''; +% +% my @payby_part_bill_event = +% grep { $payby eq $_->payby } +% sort { ( $a->freq || '1d') cmp ( $b->freq || '1d' ) # for now +% || $a->seconds <=> $b->seconds +% || $a->weight <=> $b->weight +% || $a->eventpart <=> $b->eventpart +% } +% @part_bill_event; +% +% +% if ( @payby_part_bill_event ) { + + + <% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% +% foreach my $part_bill_event ( @payby_part_bill_event ) { +% my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart; +% my $delay = duration_exact($part_bill_event->seconds); +% ( my $plandata = $part_bill_event->plandata ) =~ s/\n/<BR>/go; +% my $freq = $part_bill_event->freq || '1d'; +% +% if ( $oldfreq ne $freq ) { + + + <TR> + <TH CLASS="grid" BGCOLOR="#999999" COLSPAN=<% $cgi->param('showdisabled') ? 7 : 8 %>><% ucfirst($freq{$freq}) %> event tests for <FONT SIZE="+1"><I><% $payby{$payby} %> customers</I></FONT></TH> + </TR> + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">After</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Action</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Options</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Code</TH> + </TR> +% +% $oldfreq = $freq; +% $bgcolor = ''; +% +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_bill_event->eventpart %></A></TD> +% unless ( $cgi->param('showdisabled') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_bill_event->disabled ? 'DISABLED' : '' %></TD> +% } + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_bill_event->event %></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $delay %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_bill_event->plan %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $plandata %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="-1"> + <% $part_bill_event->eventcode %></FONT></TD> + </TR> +% } + + </TABLE> + <BR><BR> +% } +% } + + +</BODY> +</HTML> diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi new file mode 100755 index 000000000..0f6731739 --- /dev/null +++ b/httemplate/browse/part_export.cgi @@ -0,0 +1,41 @@ +<!-- mason kludge --> +<% include("/elements/header.html","Export Listing", menubar( 'Main Menu' => "$p#sysadmin" )) %> +Provisioning services to external machines, databases and APIs.<BR><BR> +<A HREF="<% $p %>edit/part_export.cgi"><I>Add a new export</I></A><BR><BR> +<SCRIPT> +function part_export_areyousure(href) { + if (confirm("Are you sure you want to delete this export?") == true) + window.location.href = href; +} +</SCRIPT> + +<% table() %> + <TR> + <TH COLSPAN=2>Export</TH> + <TH>Options</TH> + </TR> +% foreach my $part_export ( sort { +% $a->getfield('exportnum') <=> $b->getfield('exportnum') +% } qsearch('part_export',{}) ) { +% + + <TR> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD> + <TD><% $part_export->exporttype %> to <% $part_export->machine %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)</TD> + <TD> + <% itable() %> +% my %opt = $part_export->options; +% foreach my $opt ( keys %opt ) { + + <TR><TD><% $opt %></TD><TD><% encode_entities($opt{$opt}) %></TD></TR> +% } + + </TABLE> + </TD> + </TR> +% } + + +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi new file mode 100755 index 000000000..f2364b152 --- /dev/null +++ b/httemplate/browse/part_pkg.cgi @@ -0,0 +1,243 @@ +% +% +%#false laziness w/access_user.html +%my %search = (); +%my $search = ''; +%unless ( $cgi->param('showdisabled') ) { +% %search = ( 'disabled' => '' ); +% $search = "( disabled = '' OR disabled IS NULL )"; +%} +% +%my $select = '*'; +%my $orderby = 'pkgpart'; +%if ( $cgi->param('active') ) { +% +% $orderby = 'num_active DESC'; +%} +% $select = " +% +% *, +% +% ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart +% AND ( cancel IS NULL OR cancel = 0 ) +% AND ( susp IS NULL OR susp = 0 ) +% ) AS num_active, +% +% ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart +% AND ( cancel IS NULL OR cancel = 0 ) +% AND susp IS NOT NULL AND susp != 0 +% ) AS num_suspended, +% +% ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart +% AND cancel IS NOT NULL AND cancel != 0 +% ) AS num_cancelled +% +% "; +% +%#} +% +%my $conf = new FS::Conf; +%my $taxclasses = $conf->exists('enable_taxclasses'); +% +%my $html_init; +%#unless ( $cgi->param('active') ) { +% $html_init = qq! +% One or more service definitions are grouped together into a package +% definition and given pricing information. Customers purchase packages +% rather than purchase services directly.<BR><BR> +% <A HREF="${p}edit/part_pkg.cgi"><I>Add a new package definition</I></A> +% <BR><BR> +% !; +%#} +% +%my $posttotal; +%if ( $cgi->param('showdisabled') ) { +% $cgi->param('showdisabled', 0); +% $posttotal = '( <a href="'. $cgi->self_url. '">hide disabled packages</a> )'; +% $cgi->param('showdisabled', 1); +%} else { +% $cgi->param('showdisabled', 1); +% $posttotal = '( <a href="'. $cgi->self_url. '">show disabled packages</a> )'; +% $cgi->param('showdisabled', 0); +%} +% +% +%# ------ +% +%my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ]; +% +%my @header = ( '#', 'Package', 'Comment' ); +%my @fields = ( 'pkgpart', 'pkg', 'comment' ); +%my $align = 'rll'; +%my @links = ( $link, $link, '' ); +%my @style = ( '', '', '' ); +% +%#false laziness w/access_user.html +%#unless ( $cgi->param('showdisabled') ) { #its been reversed already +%if ( $cgi->param('showdisabled') ) { #its been reversed already +% push @header, 'Status'; +% push @fields, sub { shift->disabled +% ? '<FONT COLOR="#FF0000">DISABLED</FONT>' +% : '<FONT COLOR="#00CC00">Active</FONT>' +% }; +% push @links, ''; +% $align .= 'c'; +% push @style, 'b'; +%} +% +%unless ( 0 ) { #already showing only one class or something? +% push @header, 'Class'; +% push @fields, sub { shift->classname || '(none)'; }; +% $align .= 'l'; +%} +% +%#if ( $cgi->param('active') ) { +% push @header, 'Customer<BR>packages'; +% my %col = ( +% 'active' => '00CC00', +% 'suspended' => 'FF9900', +% 'cancelled' => 'FF0000', +% ); +% my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart='; +% push @fields, sub { my $part_pkg = shift; +% [ +% map { +% [ +% { +% 'data' => '<B><FONT COLOR="#'. $col{$_}. '">'. +% $part_pkg->get("num_$_"). +% '</FONT></B>', +% 'align' => 'right', +% }, +% { +% 'data' => $_, +% 'align' => 'left', +% 'link' => ( $part_pkg->get("num_$_") +% ? $cust_pkg_link. +% $part_pkg->pkgpart. +% ";magic=$_" +% : '' +% ), +% }, +% ], +% } (qw( active suspended cancelled )) +% ]; }; +% $align .= 'r'; +%#} +% +%push @header, 'Frequency'; +%push @fields, sub { shift->freq_pretty; }; +%$align .= 'l'; +% +%if ( $taxclasses ) { +% push @header, 'Taxclass'; +% push @fields, sub { shift->taxclass() || ' '; }; +% $align .= 'l'; +%} +% +%push @header, 'Plan', +% 'Data', +% 'Services'; +% #'Service', 'Quan', 'Primary'; +% +%push @fields, sub { shift->plan || '(legacy)' }, +% +% sub { +% my $part_pkg = shift; +% if ( $part_pkg->plan ) { +% +% [ map { +% /^(\w+)=(.*)$/; #or something; +% [ +% { 'data' => $1, +% 'align' => 'right', +% }, +% { 'data' => $2, +% 'align' => 'left', +% }, +% ]; +% } +% split(/\n/, $part_pkg->plandata) +% ]; +% +% } else { +% +% [ map { [ +% { 'data' => uc($_), +% 'align' => 'right', +% }, +% { +% 'data' => $part_pkg->$_(), +% 'align' => 'left', +% }, +% ]; +% } +% (qw(setup recur)) +% ]; +% +% } +% +% }, +% +% sub { +% my $part_pkg = shift; +% +% [ map { +% my $pkg_svc = $_; +% my $part_svc = $pkg_svc->part_svc; +% my $svc = $part_svc->svc; +% if ( $pkg_svc->primary_svc =~ /^Y/i ) { +% $svc = "<B>$svc (PRIMARY)</B>"; +% } +% $svc =~ s/ +/ /g; +% +% [ +% { +% 'data' => '<B>'. $pkg_svc->quantity. '</B>', +% 'align' => 'right' +% }, +% { +% 'data' => $svc, +% 'align' => 'left', +% 'link' => $p. 'edit/part_svc.cgi?'. +% $part_svc->svcpart, +% }, +% ]; +% } +% sort { $b->primary_svc =~ /^Y/i +% <=> $a->primary_svc =~ /^Y/i +% } +% $part_pkg->pkg_svc +% +% ]; +% +% }; +% +%$align .= 'lrl'; #rr'; +% +%# -------- +% +%my $count_query = 'SELECT COUNT(*) FROM part_pkg'; +%$count_query .= " WHERE $search" +% if $search; +% +% +<% include( 'elements/browse.html', + 'title' => 'Package Definitions', + 'menubar' => [ 'Main Menu' => $p ], + 'html_init' => $html_init, + 'html_posttotal' => $posttotal, + 'name' => 'package definitions', + 'query' => { 'select' => $select, + 'table' => 'part_pkg', + 'hashref' => \%search, + 'extra_sql' => "ORDER BY $orderby", + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'align' => $align, + 'style' => \@style, + ) +%> diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html new file mode 100755 index 000000000..0e61a908e --- /dev/null +++ b/httemplate/browse/part_referral.html @@ -0,0 +1,141 @@ +<% include("/elements/header.html","Advertising source Listing" ) %> + +Where a customer heard about your service. Tracked for informational purposes. +<BR><BR> + +<A HREF="<% $p %>edit/part_referral.html"><I>Add a new advertising source</I></A> +<BR><BR> +% +% my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); +% my %after; +% tie %after, 'Tie::IxHash', +% 'Today' => 0, +% 'Yesterday' => 86400, # 60sec * 60min * 24hrs +% 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days +% 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days +% 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days +% 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days +% 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days +% 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days +% 'Total' => $today, +% ; +% my %before = ( +% 'Today' => 86400, # 60sec * 60min * 24hrs +% 'Yesterday' => 0, +% 'Past week' => 86400, # 60sec * 60min * 24hrs +% 'Past 30 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 60 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 90 days' => 86400, # 60sec * 60min * 24hrs +% 'Past 6 months' => 86400, # 60sec * 60min * 24hrs +% 'Past year' => 86400, # 60sec * 60min * 24hrs +% 'Total' => 86400, # 60sec * 60min * 24hrs +% ); +% +% my $curuser = $FS::CurrentUser::CurrentUser; +% +% my $statement = "SELECT COUNT(*) FROM h_cust_main +% WHERE history_action = 'insert' +% AND refnum = ? +% AND history_date >= ? +% AND history_date < ? +% AND ". $curuser->agentnums_sql; +% my $sth = dbh->prepare($statement) +% or die dbh->errstr; +% +% my $show_agentnums = scalar($curuser->agentnums); +% +% + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2 ROWSPAN=2>Advertising source</TH> +% if ( $show_agentnums ) { + + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Agent</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% scalar(keys %after) %>>Customers</TH> +</TR> +% for my $period ( keys %after ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1><% $period %></FONT></TH> +% } + +</TR> +% +%foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% $a = 0; +% +% + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $part_referral->agentnum || $curuser->access_right('Edit global advertising sources') ) { +% $a++; +% + + <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>"> +% } + + <% $part_referral->refnum %><% $a ? '</A>' : '' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $a ) { + + <A HREF="<% $p %>edit/part_referral.html?<% $part_referral->refnum %>"> +% } + + <% $part_referral->referral %><% $a ? '</A>' : '' %></TD> +% if ( $show_agentnums ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %></TD> +% } +% for my $period ( keys %after ) { +% $sth->execute( $part_referral->refnum, +% $today-$after{$period}, +% $today+$before{$period}, +% ) or die $sth->errstr; +% my $number = $sth->fetchrow_arrayref->[0]; +% + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $number %></TD> +% } + + </TR> +% } +% +% $statement =~ s/AND refnum = \?//; +% $sth = dbh->prepare($statement) +% or die dbh->errstr; +% + + <TR> + <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD> +% for my $period ( keys %after ) { +% $sth->execute( $today-$after{$period}, +% $today+$before{$period}, +% ) or die $sth->errstr; +% my $number = $sth->fetchrow_arrayref->[0]; +% + + <TD BGCOLOR="#dddddd" ALIGN="right"><B><% $number %><B></TD> +% } + + </TR> + </TABLE> + </BODY> +</HTML> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi new file mode 100755 index 000000000..0113263fb --- /dev/null +++ b/httemplate/browse/part_svc.cgi @@ -0,0 +1,177 @@ +% +% +%#code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm +%my %flag = ( +% '' => '', +% 'D' => 'Default', +% 'F' => 'Fixed (unchangeable)', +% #'M' => 'Manual selection from inventory', +% 'M' => 'Manual selected from inventory', +% #'A' => 'Automatically fill in from inventory', +% 'A' => 'Automatically filled in from inventory', +% 'X' => 'Excluded', +%); +% +%my %search; +%if ( $cgi->param('showdisabled') ) { +% %search = (); +%} else { +% %search = ( 'disabled' => '' ); +%} +% +%my @part_svc = +% sort { $a->getfield('svcpart') <=> $b->getfield('svcpart') } +% qsearch('part_svc', \%search ); +%my $total = scalar(@part_svc); +% +%my %num_active_cust_svc = map { $_->svcpart => $_->num_cust_svc } @part_svc; +% +%if ( $cgi->param('orderby') eq 'active' ) { +% @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=> +% $num_active_cust_svc{$a->svcpart} } @part_svc; +%} elsif ( $cgi->param('orderby') eq 'svc' ) { +% @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc; +%} +% +%my %inventory_class = (); +% +% + +<% include("/elements/header.html",'Service Definition Listing', menubar( 'Main Menu' => $p) ) %> + +<SCRIPT> +function part_export_areyousure(href) { + if (confirm("Are you sure you want to delete this export?") == true) + window.location.href = href; +} +</SCRIPT> + + Service definitions are the templates for items you offer to your customers.<BR><BR> + +<FORM METHOD="POST" ACTION="<% $p %>edit/part_svc.cgi"> +<A HREF="<% $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A> +% if ( @part_svc ) { + or <SELECT NAME="clone"><OPTION></OPTION> +% foreach my $part_svc ( @part_svc ) { + + <OPTION VALUE="<% $part_svc->svcpart %>"><% $part_svc->svc %></OPTION> +% } + +</SELECT><INPUT TYPE="submit" VALUE="Clone existing service"> +% } + +</FORM><BR> + +<% $total %> service definitions +<% $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled services</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled services</a> )'; } +%> +% $cgi->param('showdisabled', ( 1 ^ $cgi->param('showdisabled') ) ); + +<% table() %> + <TR> + <TH><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> +% if ( $cgi->param('showdisabled') ) { + + <TH>Status</TH> +% } + + <TH><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> + <TH>Table</TH> + <TH><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> + <TH>Export</TH> + <TH>Field</TH> + <TH COLSPAN=2>Modifier</TH> + </TR> +% foreach my $part_svc ( @part_svc ) { +% my $svcdb = $part_svc->svcdb; +% my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } ); +% my @dfields = $svc_x->fields; +% push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge +% my @fields = +% grep { $svc_x->pvf($_) +% or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag } +% @dfields ; +% my $rowspan = scalar(@fields) || 1; +% my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart; +% + + + <TR> + <TD ROWSPAN=<% $rowspan %>><A HREF="<% $url %>"> + <% $part_svc->svcpart %></A></TD> +% if ( $cgi->param('showdisabled') ) { + + <TD ROWSPAN=<% $rowspan %>> + <% $part_svc->disabled + ? '<FONT COLOR="#FF0000"><B>Disabled</B></FONT>' + : '<FONT COLOR="#00CC00"><B>Enabled</B></FONT>' + %> + </TD> +% } + + <TD ROWSPAN=<% $rowspan %>><A HREF="<% $url %>"> + <% $part_svc->svc %></A></TD> + <TD ROWSPAN=<% $rowspan %>> + <% $svcdb %></TD> + <TD ROWSPAN=<% $rowspan %>> + <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <A HREF="<%$p%>search/<% $svcdb %>.cgi?svcpart=<% $part_svc->svcpart %>">active</A> +% if ( $num_active_cust_svc{$part_svc->svcpart} ) { + + <BR><FONT SIZE="-1">[ <A HREF="<%$p%>edit/bulk-cust_svc.html?svcpart=<% $part_svc->svcpart %>">change</A> ]</FONT> +% } + + </TD> + <TD ROWSPAN=<% $rowspan %>><% itable() %> +% +%# my @part_export = +%map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ; +% foreach my $part_export ( +% map { qsearchs('part_export', { exportnum => $_->exportnum } ) } +% qsearch('export_svc', { svcpart => $part_svc->svcpart } ) +% ) { +% + + <TR> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %>: <% $part_export->exporttype %> to <% $part_export->machine %></A></TD></TR> +% } + + </TABLE></TD> +% my($n1)=''; +% foreach my $field ( @fields ) { +% my $flag = $part_svc->part_svc_column($field)->columnflag; +% + + <% $n1 %> + <TD><% $field %></TD> + <TD><% $flag{$flag} %></TD> + + <TD> +% my $value = $part_svc->part_svc_column($field)->columnvalue; +% if ( $flag =~ /^[MA]$/ ) { +% $inventory_class{$value} +% ||= qsearchs('inventory_class', { 'classnum' => $value } ); +% + + <% $inventory_class{$value} + ? $inventory_class{$value}->classname + : "WARNING: inventory_class.classnum $value not found" %> +% } else { + + <% $value %> +% } + + </TD> +% $n1="</TR><TR>"; +% } +% + + </TR> +% } + +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi new file mode 100644 index 000000000..7dcb58a53 --- /dev/null +++ b/httemplate/browse/part_virtual_field.cgi @@ -0,0 +1,43 @@ +<% include("/elements/header.html",'Virtual field definitions', menubar('Main Menu' => $p)) %> +% +% +%my %pvfs; +%my $block; +%my $p2 = popurl(2); +%my $dbtable; +% +%foreach (qsearch('part_virtual_field', {})) { +% push @{ $pvfs{$_->dbtable} }, $_; +%} +% +% if ($cgi->param('error')) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> + <BR><BR> +% } + + +<A HREF="<%$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR> +% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { + +<H3><%$dbtable%></H3> + +<%table()%> +<TH><TD>Field name</TD><TD>Description</TD></TH> +% foreach my $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { + + <TR> + <TD></TD> + <TD> + <A HREF="<%$p2%>edit/part_virtual_field.cgi?<%$pvf->vfieldpart%>"> + <%$pvf->name%></A></TD> + <TD><%$pvf->label%></TD> + </TR> +% } + +</TABLE> +% } + +</BODY> +</HTML> + diff --git a/httemplate/browse/pay_batch.cgi b/httemplate/browse/pay_batch.cgi new file mode 100755 index 000000000..66c86d676 --- /dev/null +++ b/httemplate/browse/pay_batch.cgi @@ -0,0 +1,54 @@ +<!-- mason kludge --> +<%= include("/elements/header.html","Credit card batches", menubar( 'Main Menu' => $p,)) %> + +<BR><BR> + +<% + my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved'); +%> + +<BR> +<%= &table() %> + <TR> + <TH>Batch</TH> + <TH>First Download</TH> + <TH>Last Upload</TH> + <TH>Item Count</TH> + <TH>Amount</TH> + <TH>Status</TH> + </TR> + +<% +foreach my $pay_batch ( sort { $b->batchnum <=> $a->batchnum } + qsearch('pay_batch', {} ) +) { + + my $statement = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . + $pay_batch->batchnum; + 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]; + + my $c_statement = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . + $pay_batch->batchnum; + 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]; + +%> + + <TR> + <TD><A HREF="cust_pay_batch.cgi?<%= $pay_batch->batchnum %>"><%= $pay_batch->batchnum %></TD> + <TD><%= $pay_batch->download ? time2str("%a %b %e %T %Y", $pay_batch->download) : '' %></TD> + <TD><%= $pay_batch->upload ? time2str("%a %b %e %T %Y", $pay_batch->upload) : '' %></TD> + <TD><%= $cards %></TD> + <TD align="right"><%= $total %></TD> + <TD><%= $statusmap{$pay_batch->status} %></TD> + </TR> + +<% } %> + + </TABLE> + </BODY> +</HTML> diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html new file mode 100644 index 000000000..6c14a1006 --- /dev/null +++ b/httemplate/browse/payment_gateway.html @@ -0,0 +1,78 @@ +% +% +% my %search; +% if ( $cgi->param('showdisabled') ) { +% %search = (); +% } else { +% %search = ( 'disabled' => '' ); +% } +% +% + +<% include("/elements/header.html",'Payment gateways', menubar( + 'Main Menu' => $p, + 'Agents' => $p. 'browse/agent.cgi', +)) %> + +<A HREF="<% $p %>edit/payment_gateway.html"><I>Add a new payment gateway</I></A><BR><BR> + +<% $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled gateways</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled gateways</a> )'; } +%> + +<% table() %> +<TR> + <TH COLSPAN=<% $cgi->param('showdisabled') ? 1 : 2 %>>#</TH> + <TH>Gateway</TH> + <TH>Username</TH> + <TH>Password</TH> + <TH>Action</TH> + <TH>Options</TH> +</TR> +% foreach my $payment_gateway ( qsearch( 'payment_gateway', \%search ) ) { + + + <TR> + <TD><% $payment_gateway->gatewaynum %></TD> +% if ( !$cgi->param('showdisabled') ) { + + <TD><% $payment_gateway->disabled ? 'DISABLED' : '' %></TD> +% } + + <TD><% $payment_gateway->gateway_module %> + <FONT SIZE="-1"> + <A HREF="<%$p%>edit/payment_gateway.html?<% $payment_gateway->gatewaynum %>">(edit)</A> + <% !$payment_gateway->disabled + ? '<A HREF="'. $p. 'misc/disable-payment_gateway.cgi?'. $payment_gateway->gatewaynum.'">(disable)</A>' + : '' + %> + </FONT> + </TD> + <TD><% $payment_gateway->gateway_username %></TD> + <TD> - </TD> + <TD><% $payment_gateway->gateway_action %></TD> + <TD> + <TABLE CELLSPACING=0 CELLPADDING=0> +% my %options = $payment_gateway->options; +% foreach my $option ( keys %options ) { +% + + <TR> + <TH><% $option %>:</TH> + <TD><% $options{$option} %></TD> + </TR> +% } + + </TABLE> + </TD> + </TR> +% } + + +</TABLE> +</BODY> +</HTML> + diff --git a/httemplate/browse/pkg_class.html b/httemplate/browse/pkg_class.html new file mode 100644 index 000000000..3ec5e559b --- /dev/null +++ b/httemplate/browse/pkg_class.html @@ -0,0 +1,27 @@ +% +% +%my $html_init = +% 'Package classes define groups of packages, for reporting and '. +% 'convenience purposes.<BR><BR>'. +% qq!<A HREF="${p}edit/pkg_class.html"><I>Add a package class</I></A><BR><BR>!; +% +%my $count_query = 'SELECT COUNT(*) FROM pkg_class'; +% +%my $link = [ $p.'edit/pkg_class.html?', 'classnum' ]; +% +% +<% include( 'elements/browse.html', + 'title' => 'Package classes', + 'menubar' => [ 'Main menu' => $p, ], + 'html_init' => $html_init, + 'name' => 'package classes', + 'query' => { 'table' => 'pkg_class', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY classnum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Class', ], + 'fields' => [ 'classnum', 'classname' ], + 'links' => [ $link, $link ], + ) +%> diff --git a/httemplate/browse/rate.cgi b/httemplate/browse/rate.cgi new file mode 100644 index 000000000..9bdbe2d0c --- /dev/null +++ b/httemplate/browse/rate.cgi @@ -0,0 +1,34 @@ +% +% +%my $html_init = +% 'Rate plans, regions and prefixes for VoIP and call billing.<BR><BR>'. +% qq!<A HREF="${p}edit/rate.cgi"><I>Add a rate plan</I></A>!. +% qq! | <A HREF="${p}edit/rate_region.cgi"><I>Add a region</I></A>!. +% '<BR><BR> +% <SCRIPT> +% function rate_areyousure(href) { +% if (confirm("Are you sure you want to delete this rate plan?") == true) +% window.location.href = href; +% } +% </SCRIPT>'; +% +%my $count_query = 'SELECT COUNT(*) FROM rate'; +% +%my $link = [ $p.'edit/rate.cgi?', 'ratenum' ]; +% +% +<% include( 'elements/browse.html', + 'title' => 'Rate plans', + 'menubar' => [ 'Main menu' => $p, ], + 'html_init' => $html_init, + 'name' => 'rate plans', + 'query' => { 'table' => 'rate', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY ratenum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Rate plan', ], + 'fields' => [ 'ratenum', 'ratename' ], + 'links' => [ $link, $link ], + ) +%> diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi new file mode 100644 index 000000000..7309388c6 --- /dev/null +++ b/httemplate/browse/router.cgi @@ -0,0 +1,61 @@ +<% include("/elements/header.html",'Routers', menubar('Main Menu' => $p)) %> +% +% +%my @router = qsearch('router', {}); +%my $p2 = popurl(2); +% +% +% if ($cgi->param('error')) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT> + <BR><BR> +% } +% +%my $hidecustomerrouters = 0; +%my $hideurl = ''; +%if ($cgi->param('hidecustomerrouters') eq '1') { +% $hidecustomerrouters = 1; +% $cgi->param('hidecustomerrouters', 0); +% $hideurl = '<A HREF="' . $cgi->self_url() . '">Show customer routers</A>'; +%} else { +% $hidecustomerrouters = 0; +% $cgi->param('hidecustomerrouters', 1); +% $hideurl = '<A HREF="' . $cgi->self_url() . '">Hide customer routers</A>'; +%} +% + + +<A HREF="<%$p2%>edit/router.cgi">Add a new router</A> | <%$hideurl%> + +<%table()%> + <TR> + <TD><B>Router name</B></TD> + <TD><B>Address block(s)</B></TD> + </TR> +% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) { +% next if $hidecustomerrouters && $router->svcnum; +% my @addr_block = $router->addr_block; +% if (scalar(@addr_block) == 0) { +% push @addr_block, ' '; +% } +% + + <TR> + <TD ROWSPAN="<%scalar(@addr_block)+1%>"> + <A HREF="<%$p2%>edit/router.cgi?<%$router->routernum%>"><%$router->routername%></A> + </TD> + </TR> +% foreach my $block ( @addr_block ) { + + <TR> + <TD><%UNIVERSAL::isa($block, 'FS::addr_block') ? $block->NetAddr : ' '%></TD> + </TR> +% } + + </TR> +% } + +</TABLE> +</BODY> +</HTML> + diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi new file mode 100755 index 000000000..949bfa790 --- /dev/null +++ b/httemplate/browse/svc_acct_pop.cgi @@ -0,0 +1,71 @@ +<!-- mason kludge --> +% +% my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct +% WHERE popnum = ? ") +% or die dbh->errstr; +% + +<% include("/elements/header.html",'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> +<% table() %> + <TR> + <TH></TH> + <TH>City</TH> + <TH>State</TH> + <TH>Area code</TH> + <TH>Exchange</TH> + <TH>Local</TH> + <TH>Accounts</TH> + </TR> +% +%foreach my $svc_acct_pop ( sort { +% #$a->getfield('popnum') <=> $b->getfield('popnum') +% $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 $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="<% $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> +% } + + + <TR> + </TR> + </TABLE> + </BODY> +</HTML> + diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi new file mode 100644 index 000000000..a78f3978c --- /dev/null +++ b/httemplate/config/config-process.cgi @@ -0,0 +1,52 @@ +% +% my $conf = new FS::Conf; +% $FS::Conf::DEBUG = 1; +% my @config_items = $conf->config_items; +% +% foreach my $i ( @config_items ) { +% my @touch = (); +% my @delete = (); +% my $n = 0; +% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +% if ( $type eq '' ) { +% } elsif ( $type eq 'textarea' ) { +% if ( $cgi->param($i->key. $n) ne '' ) { +% my $value = $cgi->param($i->key. $n); +% $value =~ s/\r\n/\n/g; #browsers? +% $conf->set($i->key, $value); +% } else { +% $conf->delete($i->key); +% } +% } elsif ( $type eq 'checkbox' ) { +%# if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) { +% if ( defined $cgi->param($i->key. $n) ) { +% #$conf->touch($i->key); +% push @touch, $i->key; +% } else { +% #$conf->delete($i->key); +% push @delete, $i->key; +% } +% } elsif ( $type eq 'text' || $type eq 'select' || $type eq 'select-sub' ) { +% if ( $cgi->param($i->key. $n) ne '' ) { +% $conf->set($i->key, $cgi->param($i->key. $n)); +% } else { +% $conf->delete($i->key); +% } +% } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' ) { +% if ( scalar(@{[ $cgi->param($i->key. $n) ]}) ) { +% $conf->set($i->key, join("\n", @{[ $cgi->param($i->key. $n) ]} )); +% } else { +% $conf->delete($i->key); +% } +% } else { +% } +% $n++; +% } +% # warn @touch; +% $conf->touch($_) foreach @touch; +% $conf->delete($_) foreach @delete; +% } +% +% + +<% $cgi->redirect("config-view.cgi") %> diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi new file mode 100644 index 000000000..ff7913d78 --- /dev/null +++ b/httemplate/config/config-view.cgi @@ -0,0 +1,92 @@ +<!-- mason kludge --> +<% include("/elements/header.html",'View Configuration', menubar( 'Main Menu' => $p, + 'Edit Configuration' => 'config.cgi' ) ) %> +% my $conf = new FS::Conf; my @config_items = $conf->config_items; +% foreach my $section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { + + <A NAME="<% $section || 'unclassified' %>"></A> + <FONT SIZE="-2"> +% foreach my $nav_section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { +% if ( $section eq $nav_section ) { + + [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>] +% } else { + + [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>] +% } +% } + + </FONT><BR> + <% table("#cccccc", 2) %> + <tr> + <th colspan="2" bgcolor="#dcdcdc"> + <% ucfirst($section || 'unclassified') %> configuration options + </th> + </tr> +% foreach my $i (grep $_->section eq $section, @config_items) { + + <tr> + <td><a name="<% $i->key %>"> + <b><% $i->key %></b> - <% $i->description %> + </a></td> + <td><table border=0> +% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +% my $n = 0; +% if ( $type eq '' ) { + + <tr> + <td><font color="#ff0000">no type</font></td> + </tr> +% } elsif ( $type eq 'textarea' +% || $type eq 'editlist' +% || $type eq 'selectmultiple' ) { + + <tr> + <td bgcolor="#ffffff"> +<pre> +<% encode_entities(join("\n", $conf->config($i->key) ) ) %> +</pre> + </td> + </tr> +% } elsif ( $type eq 'checkbox' ) { + + <tr> + <td bgcolor="#<% $conf->exists($i->key) ? '00ff00">YES' : 'ff0000">NO' %></td> + </tr> +% } elsif ( $type eq 'text' || $type eq 'select' ) { + + <tr> + <td bgcolor="#ffffff"> + <% $conf->exists($i->key) ? $conf->config($i->key) : '' %> + </td></tr> +% } elsif ( $type eq 'select-sub' ) { + + <tr> + <td bgcolor="#ffffff"> + <% $conf->config($i->key) %>: + <% &{ $i->option_sub }( $conf->config($i->key) ) %> + </td> + </tr> +% } else { + + <tr><td> + <font color="#ff0000">unknown type <% $type %></font> + </td></tr> +% } +% $n++; } + + </table></td> + </tr> +% } + + </table><br><br> +% } + + +</body></html> diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi new file mode 100644 index 000000000..369314d98 --- /dev/null +++ b/httemplate/config/config.cgi @@ -0,0 +1,260 @@ +<!-- mason kludge --> +<% include("/elements/header.html",'Edit Configuration', menubar( 'Main Menu' => $p ) ) %> +<SCRIPT> +var gSafeOnload = new Array(); +var gSafeOnsubmit = new Array(); +window.onload = SafeOnload; +function SafeAddOnLoad(f) { + gSafeOnload[gSafeOnload.length] = f; +} +function SafeOnload() { + for (var i=0;i<gSafeOnload.length;i++) + gSafeOnload[i](); +} +function SafeAddOnSubmit(f) { + gSafeOnsubmit[gSafeOnsubmit.length] = f; +} +function SafeOnsubmit() { + for (var i=0;i<gSafeOnsubmit.length;i++) + gSafeOnsubmit[i](); +} +</SCRIPT> +% my $conf = new FS::Conf; my @config_items = $conf->config_items; + + +<form name="OneTrueForm" action="config-process.cgi" METHOD="POST" onSubmit="SafeOnsubmit()"> +% foreach my $section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { + + <A NAME="<% $section || 'unclassified' %>"></A> + <FONT SIZE="-2"> +% foreach my $nav_section ( qw(required billing username password UI session +% shell BIND +% ), +% '', 'deprecated') { +% if ( $section eq $nav_section ) { + + [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>] +% } else { + + [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>] +% } +% } + + </FONT><BR> + <% table("#cccccc", 2) %> + <tr> + <th colspan="2" bgcolor="#dcdcdc"> + <% ucfirst($section || 'unclassified') %> configuration options + </th> + </tr> +% foreach my $i (grep $_->section eq $section, @config_items) { + + <tr> + <td> +% my $n = 0; +% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +% #warn $i->key unless defined($type); +% +% if ( $type eq '' ) { + + + <font color="#ff0000">no type</font> +% } elsif ( $type eq 'textarea' ) { + + + <textarea name="<% $i->key. $n %>" rows=5><% "\n". join("\n", $conf->config($i->key) ) %></textarea> +% } elsif ( $type eq 'checkbox' ) { + + + <input name="<% $i->key. $n %>" type="checkbox" value="1"<% $conf->exists($i->key) ? ' CHECKED' : '' %>> +% } elsif ( $type eq 'text' ) { + + + <input name="<% $i->key. $n %>" type="<% $type %>" value="<% $conf->exists($i->key) ? $conf->config($i->key) : '' %>"> +% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { + + + <select name="<% $i->key. $n %>" <% $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>> +% +% my %hash = (); +% if ( $i->select_enum ) { +% tie %hash, 'Tie::IxHash', +% '' => '', map { $_ => $_ } @{ $i->select_enum }; +% } elsif ( $i->select_hash ) { +% if ( ref($i->select_hash) eq 'ARRAY' ) { +% tie %hash, 'Tie::IxHash', +% '' => '', @{ $i->select_hash }; +% } else { +% tie %hash, 'Tie::IxHash', +% '' => '', %{ $i->select_hash }; +% } +% } else { +% %hash = ( '' => 'WARNING: neither select_enum nor select_hash specified in Conf.pm for configuration option "'. $i->key. '"' ); +% } +% +% my %saw = (); +% foreach my $value ( keys %hash ) { +% local($^W)=0; next if $saw{$value}++; +% my $label = $hash{$value}; +% + + + <option value="<% $value %>"<% $value eq $conf->config($i->key) || ( $type eq 'selectmultiple' && grep { $_ eq $value } $conf->config($i->key) ) ? ' SELECTED' : '' %>><% $label %> +% } +% my $curvalue = $conf->config($i->key); +% if ( $conf->exists($i->key) && $curvalue +% && ! $hash{$curvalue} +% ) { +% + + + <option value="<% $conf->config($i->key) %>" SELECTED><% exists( $hash{ $conf->config($i->key) } ) ? $hash{ $conf->config($i->key) } : $conf->config($i->key) %> +% } + + + </select> +% } elsif ( $type eq 'select-sub' ) { + + + <select name="<% $i->key. $n %>"> + <option value=""> +% my %options = &{$i->options_sub}; +% my @options = sort { $a <=> $b } keys %options; +% my %saw; +% foreach my $value ( @options ) { +% local($^W)=0; next if $saw{$value}++; +% + + <option value="<% $value %>"<% $value eq $conf->config($i->key) ? ' SELECTED' : '' %>><% $value %>: <% $options{$value} %> +% } +% if ( $conf->exists($i->key) && $conf->config($i->key) && ! exists $options{$conf->config($i->key)} ) { + + <option value=<% $conf->config($i->key) %> SELECTED><% $conf->config($i->key) %>: <% &{ $i->option_sub }( $conf->config($i->key) ) %> +% } + + </select> +% } elsif ( $type eq 'editlist' ) { + + + <script> + function doremove<% $i->key. $n %>() { + fromObject = document.OneTrueForm.<% $i->key. $n %>; + for (var i=fromObject.options.length-1;i>-1;i--) { + if (fromObject.options[i].selected) + deleteOption<% $i->key. $n %>(fromObject,i); + } + } + function deleteOption<% $i->key. $n %>(object,index) { + object.options[index] = null; + } + function selectall<% $i->key. $n %>() { + fromObject = document.OneTrueForm.<% $i->key. $n %>; + for (var i=fromObject.options.length-1;i>-1;i--) { + fromObject.options[i].selected = true; + } + } + function doadd<% $i->key. $n %>(object) { + var myvalue = ""; +% if ( defined($i->editlist_parts) ) { +% foreach my $pnum ( 0 .. scalar(@{$i->editlist_parts})-1 ) { + + + if ( myvalue != "" ) { myvalue = myvalue + " "; } +% if ( $i->editlist_parts->[$pnum]{type} eq 'select' ) { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.options[object.add<% $i->key. $n . "_$pnum" %>.selectedIndex].value; + <!-- #RESET SELECT?? maybe not... --> +% } elsif ( $i->editlist_parts->[$pnum]{type} eq 'immutable' ) { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.value; +% } else { + + myvalue = myvalue + object.add<% $i->key. $n . "_$pnum" %>.value; + object.add<% $i->key. $n. "_$pnum" %>.value = ""; +% } +% } +% } else { + + myvalue = object.add<% $i->key. $n. "_1" %>.value; +% } + + var optionName = new Option(myvalue, myvalue); + var length = object.<% $i->key. $n %>.length; + object.<% $i->key. $n %>.options[length] = optionName; + } + </script> + <select multiple size=5 name="<% $i->key. $n %>"> + <option selected>----------------------------------------------------------------</option> +% foreach my $line ( $conf->config($i->key) ) { + + <option value="<% $line %>"><% $line %></option> +% } + + </select><br> + <input type="button" value="remove selected" onClick="doremove<% $i->key. $n %>()"> + <script>SafeAddOnLoad(doremove<% $i->key. $n %>); + SafeAddOnSubmit(selectall<% $i->key. $n %>);</script> + <br> + <% itable() %><tr> +% if ( defined $i->editlist_parts ) { +% my $pnum=0; foreach my $part ( @{$i->editlist_parts} ) { + + <td> +% if ( $part->{type} eq 'text' ) { + + <input type="text" name="add<% $i->key. $n."_$pnum" %>"> +% } elsif ( $part->{type} eq 'immutable' ) { + + <% $part->{value} %><input type="hidden" name="add<% $i->key. $n. "_$pnum" %>" value="<% $part->{value} %>"> +% } elsif ( $part->{type} eq 'select' ) { + + <select name="add<% $i->key. $n. "_$pnum" %>"> +% foreach my $key ( keys %{$part->{select_enum}} ) { + + <option value="<% $key %>"><% $part->{select_enum}{$key} %></option> +% } + + </select> +% } else { + + <font color="#ff0000">unknown type <% $part->type %></font> +% } + + </td> +% $pnum++; } +% } else { + + <td><input type="text" name="add<% $i->key. $n %>_0"></td> +% } + + <td><input type="button" value="add" onClick="doadd<% $i->key. $n %>(this.form)"></td> + </tr></table> +% } else { + + + <font color="#ff0000">unknown type <% $type %></font> +% } +% $n++; } + + </td> + <td><a name="<% $i->key %>"> + <b><% $i->key %></b> - <% $i->description %> + </a></td> + </tr> +% } + + </table><br> + + You may need to restart Apache and/or freeside-queued for configuration + changes to take effect.<br> + + <input type="submit" value="Apply changes"><br><br> +% } + + +</form> + +</body></html> diff --git a/httemplate/docs/ach.html b/httemplate/docs/ach.html new file mode 100644 index 000000000..b8a17c87d --- /dev/null +++ b/httemplate/docs/ach.html @@ -0,0 +1,10 @@ +<HTML> + <HEAD> + <TITLE> + Electronic check (ACH) information + </TITLE> + </HEAD> + <BODY BGCOLOR="#ffffff"> + <IMG BORDER=0 SRC="../images/ach.png"> + </BODY> +</HTML> diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html new file mode 100755 index 000000000..2aa934812 --- /dev/null +++ b/httemplate/docs/admin.html @@ -0,0 +1,41 @@ +<head> + <title>Administration</title> +</head> +<body> + <h1>Administration</h1> +</body> +<ul> + <li>Open up the root of the Freeside document tree in your web + browser. For example, if you created the Freeside document tree in + /home/httpd/html/freeside, and your web browser's DocumentRoot is + /home/httpd/html, open https://your_host/freeside/. Replace + "your_host" with the name or network address of your web server. + <li>Select <u>Configuration</u> from the main menu and update your configuration values. + + <li>Go to <u>View/Edit service definitions</u> on the main menu, and + <u>Add a new service definition</u> with <i>Table</i> <b>svc_acct</b>. + Select your domain in the <b>domsvc</b> Modifier. Set <b>Fixed</b> to define + a service locked-in to this domain, or <b>Default</b> to define a service + which may select from among this domain and the customer's domains. + + <li><table><tr> + <td> Create at least POP (Point of Presence) by selecting + <u>View/Edit POPs</u> from the main menu.</td> + <th align="left"> OR </th> + <td>If you are not doing dialup, set slipip to fixed and blank for all your + Service Definitions which have Table <b>svc_acct</b>.</td> + </tr></table> + + <li>If you are using Freeside to keep track of sales taxes, define tax + information for your locales by clicking on the <u>View/Edit locales and tax + rates</u> on the main menu. + + <li>If you would like Freeside to notify your customers when their credit + cards and other billing arrangements are about to expire, arrange for + <b>freeside-expiration-alerter</b> to be run daily by cron or similar + facility. The message it sends can be configured from the + <u>Configuration</u> choice of the main menu as <u>alerter_template</u>. + +</ul> +</body> +</html> diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html new file mode 100644 index 000000000..adaac17dc --- /dev/null +++ b/httemplate/docs/billing.html @@ -0,0 +1,68 @@ +<head> + <title>Billing</title> +</head> +<body> + <h1>Billing</h1> + <ul> + <li>Add one or more <a href="../browse/part_bill_event.cgi">Invoice events</a> implmenting your business rules for re-sending invoices, retrying cards, suspending, etc. + <li>You can bill individual customers by clicking on the <i>Bill now</i> link on the main customer view. + <li>The <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> script should be run daily to bill customers and run invoice collection events. + <li>Real-time credit card processing: Install the <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> module for your processor. Configure the <a href="../config/config-view.cgi#business-onlinepayment">business-onlinepayment</a> configuration option. Disable the default <b>Batch card</b> <a href="../browse/part_bill_event.cgi">invoice event</a> and add one for Business::OnlinePayment. + <li>Optional: Credit card expiration alerts: Customize <a href="../config/config.cgi#alerter_template">alerter_template</a> configuration option and run <a href="man/bin/freeside-expiration-alerter.html">freeside-expiration-alerter</a> daily. + <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>Typeset (LaTeX) invoice templates + <ul> + <li>Install teTeX and Ghostscript (included with most distributions). + <li>Place your logo in EPS (Encapsulated PostScript) format with size 90pt X 36pt (<code>epsffit -c 0 0 90 33 yourlogo.eps >logo.eps</code>) at <code>/usr/local/etc/freeside/conf.<i>your_datasrc</i>/logo.eps</code>. + <li>Edit the <b>invoice_latexreturnaddress</b>, <b>invoice_latexfooter</b>, <b>invoice_latexnotes</b>, and <b>invoice_latexsmallfooter</b> configuration options. If you are adventurous, edit <b>invoice_latex</b> as well. + </ul> + <li>Plaintext invoice templates + <ul> + <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> + <li>$invnum - invoice number + <li>$date - as a UNIX timestamp (see <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Format.pm">Date::Format</a> for conversion functions). + <li>$page - current page + <li>$total_pages - total pages + <li>@address - A six-element array containing the customer name, company, and address. +<!-- <li>$overdue - true if this invoice is overdue --> + </ul> + </ul> + <li>HTML invoice templates + <ul> + <li>Place your logo in PNG format at <code>/usr/local/etc/freeside/conf.<i>your_datasrc</i>/logo.png</code>. + <li>HTML invoices also use <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a>. + <li>Edit the <b>invoice_html</b> configuration option. + <li>The following configuration options can be set to override the default behaviour of using the invoice_latex* data: <b>invoice_htmlreturnaddress</b>, and <b>invoice_htmlfooter</b>, <b>invoice_htmlnotes</b>. + </ul> +<!-- <li>Batch credit card processing + <ul> + <li>After <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table. Export this table to your credit card batching. + <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table. Example code to add payments is: + <pre>use FS::cust_pay; + +# loop over all records in batch + +my $payment=create FS::cust_pay ( + 'invnum' => $invnum, + 'paid' => $paid, + '_date' => $_date, + 'payby' => $payby, + 'payinfo' => $payinfo, + 'paybatch' => $paybatch, +); + +my $error=$payment->insert; +if ( $error ) { + #process error +} + +# end loop +</pre> +All fields except paybatch are contained in the cust_pay_batch table. You can use paybatch field to track particular batches and/or particular transactions within a batch. + </ul> +<!-- <li>The <a href="man/bin/freeside-print-batch.html"><b>freeside-print-batch</b></a> script can print or email pending credit card batches for manual entry. --> + </ul> +</body> diff --git a/httemplate/docs/config.html b/httemplate/docs/config.html new file mode 100644 index 000000000..9caf3bb3a --- /dev/null +++ b/httemplate/docs/config.html @@ -0,0 +1,36 @@ +<head> + <title>Configuration files</title> +</head> +<body> + <h1>Configuration files</h1> +<font size="+1" color="#ff0000">Configuration is now done by the top-level Makefile and web interface. The instructions below are no longer necessary.</font> +<ul> + <li>Create the <b>/usr/local/etc/freeside</b> directory to hold your configuration. + <li>Setting up <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication">Apache user authetication</a> is mandatory. + <li>Create the <b>/usr/local/etc/freeside/mapsecrets</b> file, which maps Apache users to a secrets file which contains a DBI data source, username and password. Every +line in <b>/usr/local/etc/freeside/mapsecrets</b> should contain a username and +filename, separated by whitespace. Note that these are not local usernames - +they are passed from Apache. <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication"> +Apache user authetication</a> is mandatory. For example, if you had the Apache users admin, +john, and sam, +you mapsecrets file might look like: +<pre> +admin secretfile +john secretfile +sam secretfile +</pre> + <li>Next, the filename(s) referenced in <b>/usr/local/etc/freeside/mapsecrets</b> file should be created in the <b>/usr/local/etc/freeside/</b> directory. Each file contains three lines: <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI data source</a> (for example, + <tt>DBI:mysql:freeside</tt> or <tt>DBI:Pg:host=localhost;dbname=freeside</tt>), database username, and database password. + These files should not be world readable. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD">manpage for your DBD</a> for the exact syntax of a DBI data source. In a normal installation such as the example above, a single file <b>/usr/local/etc/freeside/secretfile</b> would be created - for example: +<pre> +DBI:Pg:host=localhost;dbname=freeside +dbusername +dbpassword +</pre> +<li>Create the <b>/usr/local/etc/freeside/conf.<i>datasource</i></b> directory, for example, <b>/usr/local/etc/freeside/conf.DBI:Pg:host=localhost;dbname=freeside</b> (remember to backslash-escape the ; character when creating directories in the shell: +<pre>mkdir /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=freeside +</pre> +<li>The rest of the configuration can be done with the web interface. Select <u>Configuration</u> from the main menu and update your configuration values. +</ul> +</body> +</html> diff --git a/httemplate/docs/cvv2.html b/httemplate/docs/cvv2.html new file mode 100644 index 000000000..767098537 --- /dev/null +++ b/httemplate/docs/cvv2.html @@ -0,0 +1,24 @@ +<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> + </BODY> +</HTML> diff --git a/httemplate/docs/export.html b/httemplate/docs/export.html new file mode 100755 index 000000000..c6c6abd0d --- /dev/null +++ b/httemplate/docs/export.html @@ -0,0 +1,19 @@ +<head> + <title>Exports</title> +</head> +<body> + <h1>Exports</h1> + <p>Exports allow you to provision services to remote machines, databases and + APIs. Some exports, such as <b>sqlradius</b> and + <b>sqlradius_withdomain</b>, enable a feed for retreiving rating/usage data. + <p>Exports can be added and edited under + <a href="../browse/part_export.cgi"><i>Sysadmin | View/Edit Exports</i></a>. + <p>Selecting an export on the + <a href="../edit/part_export.cgi"><i>Sysadmin | View/Edit Exports | Add a new export</i></a> page will + show more information on that specific export, including available + options, setup and usage. + <p>Exports are activated by associating them with one or more service + definitions: <a href="../browse/part_svc.cgi"><i>Sysadmin | View/Edit Service definitions<i></a>. + +</body> + 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 new file mode 100644 index 000000000..e014e963d --- /dev/null +++ b/httemplate/docs/index.html @@ -0,0 +1,32 @@ +<head> + <title>Freeside Documentation</title> +</head> +<body bgcolor="#ffffff"> + <h1>Freeside Documentation</h1> +<img src="overview-new.png"> +<h3>Installation and upgrades</h3> +<ul> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Installation">New Installation</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:RT_Installation">Installing integrated RT ticketing</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Self-Service_Installation">Signup/Self-service installation</a> + <li><a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Upgrading">Upgrading from 1.5.8 or 1.6.X</a> +</ul> +<h3>Configuration and setup</h3> +<ul> +<!-- + <li><a href="config.html">Configuration files</a> +!--> + <li><a href="admin.html">Administration</a> +<!-- + <li><a href="../index.html#admin">Administration</a> +!--> + <li><a href="export.html">Exports</a> + <li><a href="billing.html">Billing</a> +</ul> +<h3>Developer</h3> +<ul> + <li><a href="schema.html">Schema reference</a> + <li><a href="man/FS.html">Perl API</a> + <li><a href="legacy.html">Importing legacy data</a> +</ul> +</body> diff --git a/httemplate/docs/legacy.html b/httemplate/docs/legacy.html new file mode 100755 index 000000000..94efe53af --- /dev/null +++ b/httemplate/docs/legacy.html @@ -0,0 +1,39 @@ +<head> + <title>Importing legacy data</title> +</head> +<body> + <h1>Importing legacy data</h1> +<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 + <li><a name="passwd">bin/passwd.import</a> - Just import `passwd' and `shadow' or `master.passwd', no RADIUS import. + <li><a name="svc_acct">bin/svc_acct.import</a> - Import `passwd', ( `shadow' or `master.passwd' ) and RADIUS `users'. Before running bin/svc_acct.import, you need <a href="../browse/part_svc.cgi">services</a> (with table svc_acct) as follows: + <ul> + <li>Most accounts probably have entries in passwd and users (with Port-Limit nonexistant or 1) + <li>Some accounts have entries in passwd and users, but with Port-Limit 2 (or more) + <li>Some accounts might have entries in users only (Port-Limit 1) + <li>Some accounts might have entries in users only (Port-Limit >= 2) + <li>POP mail accounts have entries in passwd only, and have a particular shell. + <li>Everything else in passwd is a shell account. + </ul> +<!-- <li><a name="svc_acct_sm">bin/svc_acct_sm.import</a> - Import qmail ( `virtualdomains' and `rcpthosts' ), or sendmail ( `virtusertable' and `sendmail.cw' ) files. Before running bin/svc_acct_sm.import, you need <a href="../browse/part_svc.cgi">services</a> as follows: + <ul> + <li>Domain (table svc_acct) + <li>Mail alias (table svc_acct_sm) + </ul> +--> + <li><a name="cust_main">Importing customer data</a> + <ul> + <li>Manually + <ul> + <li>Add a <a href="../edit/cust_main.cgi">new customer</a> + <li>Add one or more packages for this customer + <li>Enter a package by clicking on the package number + <li>Pick the `Link to existing' option + </ul> + <li>Batch - You will need to write a script to import your particular legacy data. You can use eg/TEMPLATE_cust_main.import as a starting point. + </ul> +</ul> +</body> + diff --git a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack diff --git a/httemplate/docs/overview-new.dia b/httemplate/docs/overview-new.dia Binary files differnew file mode 100644 index 000000000..d9989a359 --- /dev/null +++ b/httemplate/docs/overview-new.dia diff --git a/httemplate/docs/overview-new.png b/httemplate/docs/overview-new.png Binary files differnew file mode 100644 index 000000000..bf815463b --- /dev/null +++ b/httemplate/docs/overview-new.png diff --git a/httemplate/docs/overview.dia b/httemplate/docs/overview.dia Binary files differnew file mode 100644 index 000000000..a0e34c30e --- /dev/null +++ b/httemplate/docs/overview.dia diff --git a/httemplate/docs/overview.png b/httemplate/docs/overview.png Binary files differnew file mode 100644 index 000000000..bf2dbc26c --- /dev/null +++ b/httemplate/docs/overview.png diff --git a/httemplate/docs/passwd.html b/httemplate/docs/passwd.html new file mode 100755 index 000000000..fc1dde956 --- /dev/null +++ b/httemplate/docs/passwd.html @@ -0,0 +1,23 @@ +<head> + <title>fs_passwd</title> +</head> +<body> + <h1>fs_passwd</h1> +You may use fs_passwd/fs_passwd as a "passwd", "chfn" and "chsh" replacement on your shell machine(s) to cause password, gecos and shell changes to update your freeside machine. You can also use the fs_passwd/fs_passwd.html and fs_passwd/fs_passwd.cgi to run a public password change CGI on a public web server. This can pose a security risk if not configured correctly. <b>Do not use this feature unless you understand what you are doing!</b> +<br><br>Currently it is assumed that the the crypt(3) function in the C library is the same on the Freeside machine as on the target machine. +<ul> + <li>Create a freeside account on the shell or web machine(s). + <li>Setup SSH keys: + <ul> + <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase. + <li>Append the newly-created <code>identity.pub</code> file to <code>~freeside +/.ssh/authorized_keys</code> on the shell or web machine(s). + <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~freeside/.ssh/authorized_keys2</code> on the remote machine(s). + </ul> + <li>Copy fs_passwd/fs_passwdd to /usr/local/sbin on the shell or web machine(s). (chown freeside, chmod 500) + <li>Create /usr/local/freeside on the shell or web machine(s). (chown freeside, chmod 700) + <li>Run an iteration of "fs_passwd/fs_passwd_server <i>user</i> shell.machine" as the freeside user for each shell or web machine (this is a daemon process). <i>user</i> refers to a freeside user added by <a href="man/bin/freeside-adduser.html">freeside-adduser</a>. + <li>Copy fs_passwd/fs_passwd to /usr/local/bin on the shell machine(s). (chown freeside, chmod 4755). You may link it to passwd, chfn and chsh as well. + <li>Copy fs_passwd/fs_passwd.cgi to the cgi-bin directory on your web machine(s). Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perldoc.com/perl5.6.1/pod/perlsec.html">suidperl</a> to run fs_passwd.cgi as the freeside user. +</ul> +</body> diff --git a/httemplate/docs/schema.dia b/httemplate/docs/schema.dia Binary files differnew file mode 100644 index 000000000..e00f59ce1 --- /dev/null +++ b/httemplate/docs/schema.dia diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html new file mode 100644 index 000000000..cd4914a6c --- /dev/null +++ b/httemplate/docs/schema.html @@ -0,0 +1,533 @@ + + <title>Schema reference</title> +</head> +<body> + <h1>Schema reference</h1> + Schema diagram (1.4.1): <a href="schema.png">as a giant .png</a> or <a href="schema.dia">dia source</a> (<a href="http://www.lysator.liu.se/~alla/dia/">dia homepage</a>). + <ul> + <li><a name="agent" href="man/FS/agent.html">agent</a> - Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their agent type). + <ul> + <li>agentnum - primary key + <li>agent - name of this agent + <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> + <li>typenum - primary key + <li>atype - name of this agent type + </ul> + <li><a name="cust_bill" href="man/FS/cust_bill.html">cust_bill</a> - Invoices. Declarations that a customer owes you money. The specific charges are itemized in <a href="#cust_bill_pkg">cust_bill_pkg</a>. + <ul> + <li>billpkgnum - primary_key + <li>invnum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>_date + <li>charged - amount of this invoice + <li>printed - how many times this invoice has been printed automatically + <li>closed - books closed flag, empty or `Y' + </ul> + <li><a name="cust_bill_event" href="man/FS/cust_bill_event.html">cust_bill_event</a> - Invoice event history + <ul> + <li>eventnum - primary key + <li>invnum - <a href="#cust_bill">invoice</a> + <li>eventpart - <a href="#part_bill_event">event definition</a> + <li>_date + <li>status + <li>statustext + </ul> + <li><a name="part_bill_event" href="man/FS/part_bill_event.html">part_bill_event</a> - Invoice event definitions + <ul> + <li>eventpart - primary key + <li>payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, or COMP + <li>event - event name + <li>eventcode - event action + <li>seconds - how long after the invoice date (<a href="#cust_bill">cust_bill</a>._date) events of this type are triggered + <li>weight - ordering for events with identical seconds + <li>plan - eventcode plan + <li>plandata - additional plan data + <li>disabled - Disabled flag, empty or `Y' + <li>taxclass - Texas tax class flag, empty or "none", "access", or "hosting" + </ul> + <li><a name="cust_bill_pkg" href="man/FS/cust_bill_pkg.html">cust_bill_pkg</a> - Invoice line items + <ul> + <li>invnum - (multiple) key + <li>pkgnum - <a href="#cust_pkg">package</a> or 0 for the special virtual sales tax package + <li>setup - setup fee + <li>recur - recurring fee + <li>sdate - starting date + <li>edate - ending date + <li>itemdesc - Line item description (currently used only when pkgnum is 0) + </ul> + <li><a name="cust_bill_pkg_detail" href="man/FS/cust_bill_pkg_detail.html">cust_bill_pkg_detail</a> - Invoice line items detail + <ul> + <li>detailnum - primary key + <li>pkgnum - + <li>invnum - + <li>detail - Detail description + </ul> + <li><a name="cust_credit" href="man/FS/cust_credit.html">cust_credit</a> - Credits. The equivalent of a negative <a href="#cust_bill">cust_bill</a> record. + <ul> + <li>crednum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>amount - amount credited + <li>_date + <li>otaker - order taker + <li>reason + <li>closed - books closed flag, empty or `Y' + </ul> + <li><a name="cust_credit_bill" href="man/FS/cust_credit_bill.html">cust_credit_bill</a> - Credit invoice application. Links a credit to an invoice. + <ul> + <li>creditbillnum - primary key + <li>crednum - <a href="#cust_credit">credit</a> being applied + <li>invnum - <a href="#cust_bill">invoice</a> to which credit is applied + <li>amount - amount applied + <li>_date + </ul> + <li><a name="cust_pay_refund" href="man/FS/cust_pay_refund.html">cust_credit_bill</a> - Refund payment application. Links a refund to a payment. + <ul> + <li>payrefundnum - primary key + <li>paynum - <a href="#cust_pay">payment</a> + <li>refundnum - <a href="#cust_refund">refund</a> + <li>amount - amount applied + <li>_date + </ul> + <li><a name="cust_main" href="man/FS/cust_main.html">cust_main</a> - Customers + <ul> + <li>custnum - primary key + <li>agentnum - <a href="#agent">agent</a> + <li>refnum - <a href="#part_referral">referral</a> + <li>first - name + <li>last - name + <li>ss - social security number + <li>company + <li>address1 + <li>address2 + <li>city + <li>county + <li>state + <li>zip + <li>country + <li>daytime - phone + <li>night - phone + <li>fax - phone + <li><i>ship_first</i> + <li><i>ship_last</i> + <li><i>ship_company</i> + <li><i>ship_address1</i> + <li><i>ship_address2</i> + <li><i>ship_city</i> + <li><i>ship_county</i> + <li><i>ship_state</i> + <li><i>ship_zip</i> + <li><i>ship_country</i> + <li><i>ship_daytime</i> + <li><i>ship_night</i> + <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 + <li>otaker - order taker + <li>referral_custnum + <li>comments + </ul> + (columns in <i>italics</i> are optional) + <li><a name="cust_main_invoice" href="man/FS/cust_main_invoice.html">cust_main_invoice</a> - Invoice destinations for email invoices. Note that a customer can have many email destinations for their invoice (either literal or via svcnum), but only one postal destination. + <ul> + <li>destnum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>dest - Invoice destination. Freeside supports three types of invoice delivery: send directly to a service defined in Freeside, send to an arbitrary email address, or print the invoice to a printer and have someone send it out via snail mail. Freeside determines which method to use based on the contents of the dest field. If the contents are numeric, a <a href="#svc_acct">svcnum</a> pointing to a valid service is expected in the field. If the contents are a string, a literal email address is expected to be in the field. If the special keyword `POST' is present, the snail mail method is used (which is the default if no cust_main_invoice records exist). Snail mail invoices get their address information from <A name="#cust_main">cust_main</A> and are printed with the printer defined in the configuration files. + </ul> + <li><a name="cust_main_county" href="man/FS/cust_main_county.html">cust_main_county</a> - Tax rates + <ul> + <li>taxnum - primary key + <li>state + <li>county + <li>country + <li>tax - % rate + <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> + <li>exemptnum - primary key + <li>taxnum - <a href="#cust_main_county">tax rate</a> + <li>year + <li>month + <li>amount + </ul> + <li><a name="cust_pay" href="man/FS/cust_pay.html">cust_pay</a> - Payments. Money being transferred from a customer. + <ul> + <li>paynum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>paid - amount + <li>_date + <li>payby - CARD, CHEK, LECB, BILL, or COMP + <li>payinfo - card number, P.O.#, or comp issuer + <li>paybatch - text field for tracking card processor batches + <li>closed - books closed flag, empty or `Y' + </ul> + <li><a name="cust_pay-void" href="man/FS/cust_pay_void.html">cust_pay_void</a> - Voided payments. + <ul> + <li>paynum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>paid - amount + <li>_date + <li>payby - CARD, CHEK, LECB, BILL, or COMP + <li>payinfo - card number, P.O.#, or comp issuer + <li>paybatch - text field for tracking card processor batches + <li>closed - books closed flag, empty or `Y' + <li>void_date + <li>reason + <li>otaker - order taker + </ul> + <li><a name="cust_bill_pay" href="man/FS/cust_bill_pay.html">cust_bill_pay</a> - Applicaton of a payment to a specific invoice. + <ul> + <li>billpaynum + <li>invnum - <a href="#cust_bill">invoice</a> + <li>paynum - <a href="#cust_pay">payment</a> + <li>amount + <li>_date + </ul> + <li><a name="pay_batch" href="man/FS/pay_batch.html">pay_batch</a> - Pending batch + <ul> + <li>batchnum + <li>status + <li>download + <li>upload + </ul> + <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch members + <ul> + <li>paybatchnum + <li>batchnum + <li>payby - CARD, CHEK, LECB, BILL, or COMP + <li>payinfo - account number + <li>exp - card expiration + <li>amount + <li>invnum - <a href="#cust_bill">invoice</a> + <li>custnum - <a href="#cust_main">customer</a> + <li>payname - name on card + <li>first - name + <li>last - name + <li>address1 + <li>address2 + <li>city + <li>state + <li>zip + <li>country + <li>status + </ul> + <li><a name="cust_pkg" href="man/FS/cust_pkg.html">cust_pkg</a> - Customer billing items + <ul> + <li>pkgnum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>pkgpart - <a href="#part_pkg">Package definition</a> + <li>setup - date + <li>bill - next bill date + <li>last_bill - last bill date + <li>susp - (past) suspension date + <li>expire - (future) cancellation date + <li>cancel - (past) cancellation date + <li>otaker - order taker + <li>manual_flag - If this field is set to 1, disables the automatic unsuspensiond of this package when using the <a href="config.html#unsuspendauto">unsuspendauto</a> config file. + </ul> + <li><a name="cust_refund" href="man/FS/cust_refund.html">cust_refund</a> - Refunds. The transfer of money to a customer; equivalent to a negative <a href="#cust_pay">cust_pay</a> record. + <ul> + <li>refundnum - primary key + <li>custnum - <a href="#cust_main">customer</a> + <li>refund - amount + <li>_date + <li>payby - CARD, CHEK, LECB, BILL or COMP + <li>payinfo - card number, P.O.#, or comp issuer + <li>otaker - order taker + <li>closed - books closed flag, empty or `Y' + </ul> + <li><a name="cust_credit_refund" href="man/FS/cust_credit_refund.html">cust_credit_refund</a> - Applicaton of a refund to a specific credit. + <ul> + <li>creditrefundnum - primary key + <li>crednum - <a href="#cust_credit">credit</a> + <li>refundnum - <a href="#cust_refund">refund</a> + <li>amount + <li>_date + </ul> + <li><a name="cust_svc" href="man/FS/cust_svc.html">cust_svc</a> - Customer services + <ul> + <li>svcnum - primary key + <li>pkgnum - <a href="#cust_pkg">package</a> + <li>svcpart - <a href="#part_svc">Service definition</a> + </ul> + <li><a name="nas" href="man/FS/nas.html">nas</a> - Network Access Server (terminal server) + <ul> + <li>nasnum - primary key + <li>nas - NAS name + <li>nasip - NAS ip address + <li>nasfqdn - NAS fully-qualified domain name + <li>last - timestamp indicating the last instant the NAS was in a known state (used by the session monitoring). + </ul> + <li><a name="part_pkg" href="man/FS/part_pkg.html">part_pkg</a> - Package definitions + <ul> + <li>pkgpart - primary key + <li>pkg - package name + <li>comment - non-customer visable package comment + <li>promo_code - promotional code + <li><i>deprecated</i> setup - setup fee expression + <li>freq - recurring frequency (months) + <li><i>deprecated</i> recur - recurring fee expression + <li>setuptax - Setup fee tax exempt flag, empty or `Y' + <li>recurtax - Recurring fee tax exempt flag, empty or `Y' + <li>plan - price plan + <li><i>deprecated</i> plandata - additional price plan data + <li>disabled - Disabled flag, empty or `Y' + </ul> + <li><a name="part_pkg_option" href="man/FS/part_pkg_option.html">part_pkg_option</a> - Package definition options + <ul> + <li>optionnum - primary key + <li>pkgpart - <a href="#part_pkg">Package definition</a> + <li>optionname - option name + <li>optionvalue - option value + </ul> + <li><a name="reg_code" href="man/FS/reg_code.html">reg_code</A> - One-time registration codes + <ul> + <li>codenum - primary key + <li>code + <li>agentnum - <a href="#agent">Agent</a> + </ul> + <li><a name="reg_code_pkg" href="man/FS/reg_code_pkg.html">reg_code_pkg</A> - Registration code link to package definitions + <ul> + <li>codepkgnum - primary key + <li>codenum - <a href="#reg_code">Registration code</a> + <li>pkgpart - <a href="#part_pkg">Package definition</a> + </ul> + <li><a name="part_referral" href="man/FS/part_referral.html">part_referral</a> - Referral listing + <ul> + <li>refnum - primary key + <li>referral - referral + </ul> + <li><a name="part_svc" href="man/FS/part_svc.html">part_svc</a> - Service definitions + <ul> + <li>svcpart - primary key + <li>svc - name of this service + <li>svcdb - table used for this service: svc_acct, svc_forward, svc_domain, svc_charge or svc_wo + <li>disabled - Disabled flag, empty or `Y' +<!-- <li><i>table</i>__<i>field</i> - Default or fixed value for <i>field</i> in <i>table</i> + <li><i>table</i>__<i>field</i>_flag - null, D or F +--> + </ul> + <li><a name="part_svc_column" href="man/FS/part_svc_column.html">part_svc_column</a> + <ul> + <li>columnnum - primary key + <li>svcpart - <a href="#part_svc">Service definition</a> + <li>columnname - column name in part_svc.svcdb table + <li>columnvalue - default or fixed value for the column + <li>columnflag - null, D or F + </ul> + <li><a name="pkg_svc" href="man/FS/pkg_svc.html">pkg_svc</a> + <ul> + <li>pkgsvcnum - primary key + <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> + <li>exportsvcnum - primary key + <li>svcpart - <a href="#part_svc">Service definition</a> + <li>exportnum - <a href="#exportnum">Export</a> + </ul> + <li><a name="part_export" href="man/FS/part_export.html">part_export</a> - Export to external provisioning + <ul> + <li>exportnum - primary key + <li>machine - Machine name + <li>exporttype - Export type + <li>nodomain - blank or Y: usernames are exported to this service with no domain + </ul> + <li><a name="part_export_option" href="man/FS/part_export_option.html">part_export_option</a> - provisioning options + <ul> + <li>optionnum - primary key + <li>exportnum - <a href="#part_export">Export</a> + <li>optionname - option name + <li>optionvalue - option value + </ul> + <li><a name="port" href="man/FS/port.html">port</a> - individual port on a <a href="#nas">nas</a> + <ul> + <li>portnum - primary key + <li>ip - IP address of this port + <li>nasport - port number on the NAS + <li>nasnum - <a href="#nas">NAS</a> + </ul> + <li><a name="prepay_credit" href="man/FS/prepay_credit.html">prepay_credit</a> - prepaid cards + <ul> + <li>prepaynum - primary key + <li>identifier - text or numeric string of prepaid card + <li>amount - amount of prepayment + <li>seconds - prepaid time instead of (or in addition to) monetary value + <li>agentnum - optional agent assignment for prepaid cards + </ul> + <li><a name="session" href="man/FS/session.html">session</a> + <ul> + <li>sessionnum - primary key + <li>portnum - <a href="#port">Port</a> + <li>svcnum - <a href="#svc_acct">Account</a> + <li>login - timestamp indicating the beginning of this user session. + <li>logout - timestamp indicating the end of this user session. May be null, which indicates a currently open session. + </ul> + + <li><a name="svc_acct" href="man/FS/svc_acct.html">svc_acct</a> - Accounts + <ul> + <li>svcnum - <a href="#cust_svc">primary key</a> + <li>username + <li>_password + <li>sec_phrase - security phrase + <li>popnum - <a href="#svc_acct_pop">Point of Presence</a> + <li>uid + <li>gid + <li>finger - GECOS + <li>dir + <li>shell + <li>quota - (unimplementd) + <li>slipip - IP address + <li>seconds + <li>domsvc + <li>radius_<i>Radius_Reply_Attribute</i> - Radius-Reply-Attribute + <li>rc_<i>Radius_Check_Attribute</i> - Radius-Check-Attribute + </ul> + <li><a name="svc_acct_pop" href="man/FS/svc_acct_pop.html">svc_acct_pop</a> - Points of Presence + <ul> + <li>popnum - primary key + <li>city + <li>state + <li>ac - area code + <li>exch - exchange + <li>loc - rest of number + </ul> + <li><a name="part_pop_local" href="man/FS/part_pop_local.html">part_pop_local</a> - Local calling areas + <ul> + <li>localnum - primary key + <li>popnum - primary key + <li>city + <li>state + <li>npa - area code + <li>nxx - exchange + </ul> + <li><a name="svc_domain" href="man/FS/svc_domain.html">svc_domain</a> - Domains + <ul> + <li>svcnum - <a href="#cust_svc">primary key</a> + <li>domain + </ul> + <li><a name="svc_forward" href="man/FS/svc_forward.html">svc_forward</a> - Mail forwarding aliases + <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 - 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> + <li>recnum - primary key + <li>svcnum - <a href="#svc_domain">Domain</a> (by svcnum) + <li>reczone - zone for this line + <li>recaf - address family, usually <b>IN</b> + <li>rectype - type for this record (<b>A</b>, <b>MX</b>, etc.) + <li>recdata - data for this record + </ul> + <li><a name="svc_www" href="man/FS/svc_www.html">svc_www</a> + <ul> + <li>svcnum - <a href="#cust-svc">primary key</a> + <li>recnum - <a href="#domain_record">host</a> + <li>usersvc - <a href="#svc_acct">account</a> + </ul> + <li><a name="type_pkgs" href="man/FS/type_pkgs.html">type_pkgs</a> + <ul> + <li>typepkgnum - primary key + <li>typenum - <a href="#agent_type">agent type</a> + <li>pkgpart - <a href="#part_pkg">Package definition</a> + </ul> + <li><a name="queue" href="man/FS/queue.html">queue</a> - job queue + <ul> + <li>jobnum - primary key + <li>job + <li>_date + <li>status + <li>statustext + <li>svcnum + </ul> + <li><a name="queue_arg" href="man/FS/queue_arg.html">queue_arg</a> - job arguments + <ul> + <li>argnum - primary key + <li>jobnum - <a href="#queue">job</a> + <li>arg - argument + </ul> + <li><a name="queue_depend" href="man/FS/queue_depend.html">queue_depend</a> - job dependancies + <ul> + <li>dependnum - primary key + <li>jobnum - source jobnum + <li>depend_jobnum - dependancy jobnum + </ul> + <li><a name="radius_usergroup" href="man/FS/radius_usergroup.html">radius_usergroup</a> - Link users to RADIUS groups. + <ul> + <li>usergroupnum - primary key + <li>svcnum - <a href="#svc_acct">account</a> + <li>groupname + </ul> + <li><a name="rate" href="man/FS/rate.html">rate</a> - Call rate plans + <ul> + <li>ratenum - primary key + <li>ratename + </ul> + <li><a name="rate_detail" href="man/FS/rate_detail.html">rate_detail</a> - Call rate detail + <ul> + <li>ratedetailnum - primary key + <li>ratenum - <a href="#rate">rate plan</a> + <li>orig_regionnum - call origination <a href="#rate_region">region</a> + <li>dest_regionnum - call destination <a href="#rate_region">region</a> + <li>min_included - included minutes + <li>min_charge - charge per minute + <li>sec_granularity - granularity in seconds, i.e. 6 or 60 + </ul> + <li><a name="rate_region" href="man/FS/rate_region.html">rate_region</a> - Call rate region + <ul> + <li>regionnum - primary key + <li>regionname + </ul> + <li><a name="rate_prefix" href="man/FS/rate_prefix.html">rate_prefix</a> - Call rate prefix + <ul> + <li>prefixnum - primary key + <li>regionnum - <a href="#rate_region">rate region</a> + <li>countrycode + <li>npa + <li>nxx + </ul> + <li><a name="msgcat" href="man/FS/msgcat.html">msgcat</a> - i18n message catalog + <ul> + <li>msgnum - primary key + <li>msgcode - message code + <li>locale - locale + <li>msg - Message text + </ul> + <li><a name="clientapi_session" href="man/FS/clientapi_session.html">clientapi_session</a> - ClientAPI session store + <ul> + <li>sessionnum - primary key + <li>sessionid - session ID + <li>namespace - session namespace + </ul> + <li><a name="clientapi_session_field" href="man/FS/clientapi_session_field.html">clientapi_session_field</a> - Client API session store data + <ul> + <li>fieldnum - primary key + <li>sessionnum - <a href="#session">session</a> + <li>fieldname + <li>fieldvalue + </ul> + </ul> +</body> diff --git a/httemplate/docs/schema.png b/httemplate/docs/schema.png Binary files differnew file mode 100644 index 000000000..d0392e76f --- /dev/null +++ b/httemplate/docs/schema.png diff --git a/httemplate/docs/session.html b/httemplate/docs/session.html new file mode 100644 index 000000000..72e16424e --- /dev/null +++ b/httemplate/docs/session.html @@ -0,0 +1,59 @@ +<head> + <title>Session monitor</title> +</head> +<body> +<h1>Session monitor</h1> +<h2>Installation</h2> +For security reasons, the client portion of the session montior may run on one +or more external public machine(s). On these machines, install: +<ul> + <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at l +east 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series. Don't enable experimental features like threads or the PerlIO abstraction layer.) + <li><a href="man/FS/SessionClient.html">FS::SessionClient</a> (copy the fs_session/FS-SessionClient directory to the external machine, then: perl Makefile.PL; make; make install) +</ul> +Then: +<ul> + <li>Add the user `freeside' to the the external machine. + <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user). + <li>touch /usr/local/freeside/fs_sessiond_socket; chown freeside /usr/local/freeside/fs_sessiond_socket; chmod 600 /usr/local/freeside/fs_sessiond_socket + <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s). + <li>Run <pre>fs_session_server <i>user</i> <i>machine</i></pre> on the Freeside machine. + <ul> + <li><i>user</i> is a user from the mapsecrets file. + <li><i>machine</i> is the name of the external machine. + </ul> +</ul> +<h2>Usage</h2> +<ul> + <li>Web + <ul> + <li>Copy FS-SessionClient/cgi/login.cgi and logout.cgi to your web + server's document space. + <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run login.cgi and logout.cgi as the freeside user. + </ul> + <li>Command-line + <br><pre>freeside-login username ( portnum | ip | nasnum nasport ) +freeside-logout username ( portnum | ip | nasnum nasport )</pre> + <ul> + <li><i>username</i> is a customer username from the svc_acct table + <li><i>portnum</i>, <i>ip</i> or <i>nasport</i> and <i>nasnum</i> uniquely identify a port in the <a href="schema.html#port">port</a> database table. + </ul> + <li>RADIUS - One of: + <ul> + <li>Run the <b>freeside-sqlradius-radacctd</b> daemon to import radacct + records from all configured sqlradius exports: + <tt>freeside-sqlradius-radacctd username</tt> + <li>Configure your RADIUS server's login and logout callbacks to use the command-line <tt>freeside-login</tt> and <tt>freeside-logout</tt> utilites. + <li> <i>(incomplete)</i>Use the <b>fs_radlog/fs_radlogd</b> tool to + import records from a text radacct file. + </ul> +</ul> +<h2>Callbacks</h2> +<ul> + <li>Sesstion start - The command(s) specified in the <a href="config.html#session-start">session-start</a> configuration file are executed on the Freeside machine. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on. + <li>Session end - The command(s) specified in the <a href="config.html#session-stop">session-stop</a> configuration file are executed on the Freeside machine. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on. +</ul> +<h2>Dropping expired users</h2> +Run <pre>bin/freeside-session-kill username</pre> periodically from cron. +</body> +</html> diff --git a/httemplate/docs/signup.html b/httemplate/docs/signup.html new file mode 100644 index 000000000..97d7aa794 --- /dev/null +++ b/httemplate/docs/signup.html @@ -0,0 +1,54 @@ +<head> + <title>Signup server</title> +</head> +<body> + <h1>Signup server</h1> +For security reasons, the signup server should run on an external public +webserver. On this machine, install: +<ul> + <li>A web server, such as <a href="http://www.apache-ssl.org">Apache-SSL</a> or <a href="http://www.apache.org">Apache</a> + <li><a href="ftp://ftp.cs.hut.fi/pub/ssh/">SSH</a> + <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at least 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series. Don't enable experimental features like threads or the PerlIO abstraction layer.) + <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://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> +Then: +<ul> + <li>Add the user `freeside' to the the external machine. + <li>Copy or symlink fs_signup/FS-SignupClient/cgi/signup.cgi into the web server's document space. + <li>When linking to signup.cgi, you can include a referring custnum in the URL as follows: <code>http://public.web.server/path/signup.cgi?ref=1542</code> + <li>Enable CGI execution for files with the `.cgi' extension. (with <a href="http://www.apache.org/docs/mod/mod_mime.html#addhandler">Apache</a>) + <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user). + <li>touch /usr/local/freeside/fs_signupd_socket; chown freeside /usr/local/freeside/fs_signupd_socket; chmod 600 /usr/local/freeside/fs_signupd_socket + <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run signup.cgi as the freeside user. + <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s). + <li>Run <pre>fs_signup_server <i>user</i> <i>machine</i> <i>agentnum</i> <i>refnum</i></pre> on the Freeside machine. + <ul> + <li><i>user</i> is a user from the mapsecrets file. + <li><i>machine</i> is the name of the external machine. + <li><i>agentnum</i> and <i>refnum</i> are the <a href="schema.html#agent">agent</a> and <a href="schema.html#part_referral">referral</a>, respectively, to use for customers who sign up via this signup server. + </ul> +</ul> +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 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> + <li>$ac - area code of selected POP + <li>$exch - exchange of selected POP + <li>$loc - local part of selected POP + <li>$username + <li>$password + <li>$email_name - first and last name + <li>$pkg - package name + </ul> + <li>If you create a <b>/usr/local/freeside/signup.html</b> file on the external machine, it will be used as a template for the form HTML. This requires the template to be constructed appropriately; probably best to start with the example file included as <b>fs_signup/FS-SignupClient/cgi/signup.html</b>. + <li>If there are any entries in the <i>prepay_credit</i> table, a user can enter a string matching the <b>identifier</i> column to receive the credit specified in the <b>amount</b> column, and/or the time specified in the <b>seconds</b> column (for use with the <a href="session.html">session monitor</a>), after which that <b>identifier</b> is no longer valid. This can be used to implement pre-paid "calling card" type signups. The <i>bin/generate-prepay</i> script can be used to populate the <i>prepay_credit</i> table. +</ul> +</body> diff --git a/httemplate/docs/ssh.html b/httemplate/docs/ssh.html new file mode 100755 index 000000000..d2c501e35 --- /dev/null +++ b/httemplate/docs/ssh.html @@ -0,0 +1,16 @@ +<head> + <title>Unattended SSH</title> +</head> +<body> + <h1>Unattended SSH</h1> + <br><a name=ssh>Unattended remote login</a> - Freeside can login to remote machines unattended using SSH. This can pose a security risk if not configured correctly, and will allow an intruder who breaks into your freeside machine full access to your remote machines. <b>Do not use this feature unless you understand what you are doing!</b> + <ul> + <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase. + <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s). + <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s). + <li>You may need to set <code>PermitRootLogin without-password</code> (meaning with keys only) in your <code>sshd_config</code> file on the remote machine(s). + <li>You may want to set <code>ForwardX11 = no</code> in <code>~root/.ssh/config</code> to prevent spurious errors if your distribution turns on X11 forwarding by default. + </ul> + +</body> + diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi new file mode 100755 index 000000000..69bbb9b22 --- /dev/null +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -0,0 +1,185 @@ +% +% +%my $error =''; +%my $pkgnum = ''; +%if ( $cgi->param('error') ) { +% $error = $cgi->param('error'); +% $pkgnum = $cgi->param('pkgnum'); +% if ( $error eq '_bill_areyousure' ) { +% my $bill = $cgi->param('bill'); +% $error = "You are attempting to set the next bill date to $bill, which is +% in the past. This will charge the customer for the interval +% from $bill until now. Are you sure you want to do this? ". +% '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">'; +% } +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "no pkgnum"; +% $pkgnum = $1; +%} +% +%#get package record +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%die "No package!" unless $cust_pkg; +%my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); +% +%if ( $error ) { +% #$cust_pkg->$_(str2time($cgi->param($_)) foreach qw(setup bill); +% $cust_pkg->setup(str2time($cgi->param('setup'))); +% $cust_pkg->bill(str2time($cgi->param('bill'))); +% $cust_pkg->last_bill(str2time($cgi->param('last_bill'))); +%} +% +%#my $custnum = $cust_pkg->getfield('custnum'); +% + + +<% include("/elements/header.html",'Customer package - Edit dates') %> +% +%#, menubar( +%# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", +%# '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'), +% $cust_pkg->getfield('cancel'), +% $cust_pkg->getfield('expire'), +%); +%my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); +%my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); +%my $otaker = $cust_pkg->getfield('otaker'); +% +% + + +<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +% if ( $error ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT> +% } +% +% +%#my $format = "%c %z (%Z)"; +%my $format = "%m/%d/%Y %T %z (%Z)"; +% +%#false laziness w/view/cust_main/packages.html +%#my( $billed_or_prepaid, +%my( $last_bill_or_renewed, $next_bill_or_prepaid_until ); +%unless ( $part_pkg->is_prepaid ) { +% #$billed_or_prepaid = 'billed'; +% $last_bill_or_renewed = 'Last bill'; +% $next_bill_or_prepaid_until = 'Next bill'; +%} else { +% #$billed_or_prepaid = 'prepaid'; +% $last_bill_or_renewed = 'Renewed'; +% $next_bill_or_prepaid_until = 'Prepaid until'; +%} +% +% + + +<% ntable("#cccccc",2) %> + + <TR> + <TD ALIGN="right">Package number</TD> + <TD BGCOLOR="#ffffff"><% $pkgnum %></TD> + </TR> + + <TR> + <TD ALIGN="right">Package</TD> + <TD BGCOLOR="#ffffff"><% $pkg %></TD> + </TR> + + <TR> + <TD ALIGN="right">Comment</TD> + <TD BGCOLOR="#ffffff"><% $comment %></TD> + </TR> + + <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 ID="setup_text" VALUE="<% ( $setup ? time2str($format, $setup) : "" ) %>"> + <IMG SRC="../images/calendar.png" ID="setup_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> + </TR> + + <TR> + <TD ALIGN="right"><% $last_bill_or_renewed %> date</TD> + <TD> + <INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="<% ( $cust_pkg->last_bill ? time2str($format, $cust_pkg->last_bill) : "" ) %>"> + <IMG SRC="../images/calendar.png" ID="last_bill_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> + </TR> + + <TR> + <TD ALIGN="right"><% $next_bill_or_prepaid_until %> date</TD> + <TD> + <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> +% if ( $susp ) { + + <TR> + <TD ALIGN="right">Suspension date</TD> + <TD BGCOLOR="#ffffff"><% time2str($format, $susp) %></TD> + </TR> +% } + + + <TR> + <TD ALIGN="right">Expiration date</TD> + <TD> + <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> +% if ( $cancel ) { + + <TR> + <TD ALIGN="right">Cancellation date</TD> + <TD BGCOLOR="#ffffff"><% time2str($format, $cancel) %></TD> + </TR> +% } + + +</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> +</HTML> diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html new file mode 100644 index 000000000..d447512c2 --- /dev/null +++ b/httemplate/edit/access_group.html @@ -0,0 +1,46 @@ +<% include( 'elements/edit.html', + 'name' => 'Internal Access Group', + 'table' => 'access_group', + 'labels' => { + 'groupnum' => 'Group number', + 'groupname' => 'Group name', + }, + + 'viewall_dir' => 'browse', + + 'html_bottom' => + sub { + my $access_group = shift; + + "<BR>Group virtualized to customers of agents:<BR>". + ntable("#cccccc",2). + '<TR><TD>'. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_group, + 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + 'name_col' => 'agent', + 'target_link' => $p.'edit/agent.cgi?', + 'disable-able' => 1, + ). + '</TR></TD></TABLE>'. + + "<BR>Group rights:<BR>". + ntable("#cccccc",2). + '<TR><TD>'. + include( '/elements/checkboxes-table-name.html', + 'source_obj' => $access_group, + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => + 'FS::access_group', + }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + ). + '</TR></TD></TABLE>' + + ; + }, + ) +%> diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html new file mode 100644 index 000000000..df580a20d --- /dev/null +++ b/httemplate/edit/access_user.html @@ -0,0 +1,39 @@ +<% include( 'elements/edit.html', + 'name' => 'Internal User', + 'table' => 'access_user', + 'fields' => [ + 'username', + { field=>'_password', type=>'password' }, + 'last', + 'first', + { field=>'disabled', type=>'checkbox', value=>'Y' }, + ], + 'labels' => { + 'usernum' => 'User number', + 'username' => 'Username', + '_password' => 'Password', + 'last' => 'Last name', + 'first' => 'First name', + 'disabled' => 'Disable employee', + }, + 'viewall_dir' => 'browse', + 'html_bottom' => + sub { + my $access_user = shift; + + '<BR>Internal Access Groups<BR>'. + ntable("#cccccc",2). + '<TR><TD>'. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_user, + 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + 'name_col' => 'groupname', + 'target_link' => $p.'edit/access_group.html?', + #'disable-able' => 1, + ). + '</TR></TD></TABLE>' + ; + }, + ) +%> diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi new file mode 100755 index 000000000..ce514a680 --- /dev/null +++ b/httemplate/edit/agent.cgi @@ -0,0 +1,115 @@ +% +% +%my $agent; +%if ( $cgi->param('error') ) { +% $agent = new FS::agent ( { +% map { $_, scalar($cgi->param($_)) } fields('agent') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $agent = qsearchs( 'agent', { 'agentnum' => $1 } ); +%} else { #adding +% $agent = new FS::agent {}; +%} +%my $action = $agent->agentnum ? 'Edit' : 'Add'; +%my $hashref = $agent->hashref; +% +%my $conf = new FS::Conf; +% +% + + +<% include("/elements/header.html","$action Agent", menubar( + 'Main Menu' => $p, + 'View all agents' => $p. 'browse/agent.cgi', +)) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/agent.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $hashref->{agentnum} %>"> +Agent #<% $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %> + +<% &ntable("#cccccc", 2, '') %> + +<TR> + <TH ALIGN="right">Agent</TH> + <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> +% 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') %> +% } + + + </SELECT></TD> + </TR> + + <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> + </TR> + + <TR> + <TD ALIGN="right"><!--Program--></TD> + <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<% $hashref->{prog} %>"></TD> + </TR> +% if ( $conf->config('ticket_system') ) { +% my $default_queueid = $conf->config('ticket_system-default_queueid'); +% my $default_queue = FS::TicketSystem->queue($default_queueid); +% $default_queue = "(default) $default_queueid: $default_queue" +% if $default_queueid; +% my %queues = FS::TicketSystem->queues(); +% my @queueids = sort { $a <=> $b } keys %queues; +% + + <TR> + <TD ALIGN="right">Ticketing queue</TD> + <TD> + <SELECT NAME="ticketing_queueid"> + <OPTION VALUE=""><% $default_queue %> +% foreach my $queueid ( @queueids ) { + + <OPTION VALUE="<% $queueid %>" <% $agent->ticketing_queueid == $queueid ? ' SELECTED' : '' %>><% $queueid %>: <% $queues{$queueid} %> +% } + + </SELECT> + </TD> + </TR> +% } + + + <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> + +<BR><INPUT TYPE="submit" VALUE="<% $hashref->{agentnum} ? "Apply changes" : "Add agent" %>"> + </FORM> + </BODY> +</HTML> diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html new file mode 100644 index 000000000..08a2fa6bf --- /dev/null +++ b/httemplate/edit/agent_payment_gateway.html @@ -0,0 +1,70 @@ +% +% +%$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; +%my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +%die "agentnum $1 not found" unless $agent; +% +%#my @agent_payment_gateway; +%if ( $cgi->param('error') ) { +%} +% +%my $action = 'Add'; +% +% + + +<% include("/elements/header.html","$action payment gateway override for ". $agent->agent, menubar( + 'Main Menu' => $p, + #'View all payment gateways' => $p. 'browse/payment_gateway.html', + 'View all agents' => $p. 'browse/agent.html', +)) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/agent_payment_gateway.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>"> + +Use gateway <SELECT NAME="gatewaynum"> +% foreach my $payment_gateway ( +% qsearch('payment_gateway', { 'disabled' => '' } ) +% ) { +% + + <OPTION VALUE="<% $payment_gateway->gatewaynum %>"><% $payment_gateway->gateway_module %> (<% $payment_gateway->gateway_username %>) +% } + +</SELECT> +<BR><BR> + +for <SELECT NAME="cardtype" MULTIPLE> +% foreach my $cardtype ( +% "", +% "VISA card", +% "MasterCard", +% "Discover card", +% "American Express card", +% "Diner's Club/Carte Blanche", +% "enRoute", +% "JCB", +% "BankCard", +% "Switch", +% "Solo", +% 'ACH', +%) { + + <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %> +% } + +</SELECT> +<BR><BR> + +(optional) when invoice contains only items of taxclass <INPUT TYPE="text" NAME="taxclass"> +<BR><BR> + +<INPUT TYPE="submit" VALUE="Add gateway override"> +</FORM> +</BODY> +</HTML> diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi new file mode 100755 index 000000000..5438e5c3b --- /dev/null +++ b/httemplate/edit/agent_type.cgi @@ -0,0 +1,57 @@ +% +% +%my($agent_type); +%if ( $cgi->param('error') ) { +% $agent_type = new FS::agent_type ( { +% map { $_, scalar($cgi->param($_)) } fields('agent') +% } ); +%} elsif ( $cgi->keywords ) { #editing +% my( $query ) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $agent_type=qsearchs('agent_type',{'typenum'=>$1}); +%} else { #adding +% $agent_type = new FS::agent_type {}; +%} +%my $action = $agent_type->typenum ? 'Edit' : 'Add'; +% +% +<% include("/elements/header.html","$action Agent Type", menubar( + 'Main Menu' => "$p", + 'View all agent types' => "${p}browse/agent_type.cgi", +)) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% popurl(1) %>process/agent_type.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="typenum" VALUE="<% $agent_type->typenum %>"> +Agent Type #<% $agent_type->typenum || "(NEW)" %> +<BR> + +Agent Type +<INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="<% $agent_type->atype %>"> +<BR><BR> + +Select which packages agents of this type may sell to customers<BR> +<% ntable("#cccccc", 2) %><TR><TD> +<% include('/elements/checkboxes-table.html', + 'source_obj' => $agent_type, + 'link_table' => 'type_pkgs', + 'target_table' => 'part_pkg', + 'name_callback' => sub { $_[0]->pkg. ' - '. $_[0]->comment; }, + 'target_link' => $p.'edit/part_pkg.cgi?', + 'disable-able' => 1, + + ) +%> +</TD></TR></TABLE> +<BR> + +<INPUT TYPE="submit" VALUE="<% $agent_type->typenum ? "Apply changes" : "Add agent type" %>"> + + </FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/bulk-cust_svc.html b/httemplate/edit/bulk-cust_svc.html new file mode 100644 index 000000000..f2efc3ff9 --- /dev/null +++ b/httemplate/edit/bulk-cust_svc.html @@ -0,0 +1,99 @@ +<% include("/elements/header.html", 'Bulk customer service change', + menubar( + 'Main Menu' => $p, + ), + ) +%> + +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> + +<% include('/elements/progress-init.html', + 'OneTrueForm', + [qw( old_svcpart new_svcpart pkgpart )], + 'process/bulk-cust_svc.cgi', + $p.'browse/part_svc.cgi', + ) +%> + +<FORM NAME="OneTrueForm"> +% +% $cgi->param('svcpart') =~ /^(\d+)$/ +% or die "illegal svcpart: ". $cgi->param('svcpart'); +% +% my $old_svcpart = $1; +% my $src_part_svc = qsearchs('part_svc', { 'svcpart' => $old_svcpart } ) +% or die "unknown svcpart: $old_svcpart"; +% + + +<INPUT NAME="old_svcpart" TYPE="hidden" VALUE="<% $old_svcpart %>"> +Change <!-- customer +<B><% $src_part_svc->svcpart %>: <% $src_part_svc->svc %></B> services +<BR> +--> + +<SELECT NAME="pkgpart"> +% my $num_cust_svc = $src_part_svc->num_cust_svc; +% if ( $num_cust_svc > 1 ) { + + <OPTION VALUE="">all <% $num_cust_svc %> <% $src_part_svc->svc %> services +% } else { + + <OPTION VALUE="">the <% $num_cust_svc %> <% $src_part_svc->svc %> service +% } +% +% my $num_unlinked = $src_part_svc->num_cust_svc(0); +% if ( $num_unlinked ) { +% + + <OPTION VALUE="0">the <% $num_unlinked %> unlinked <% $src_part_svc->svc %> services +% } +% foreach my $schwartz ( +% grep { $_->[1] } +% map { [ $_, $src_part_svc->num_cust_svc($_->pkgpart) ] } +% qsearch('part_pkg', {} ) +% ) { +% my( $part_pkg, $num_cust_svc ) = @$schwartz; +% + + <OPTION VALUE="<% $part_pkg->pkgpart %>">the <% $num_cust_svc %> + <% $src_part_svc->svc %> service<% $num_cust_svc > 1 ? 's in' : ' in a' %> + <% $part_pkg->pkg %> package<% $num_cust_svc > 1 ? 's' : '' %> +% } + +</SELECT> +<BR> + +to new service definition +<SELECT NAME="new_svcpart"> +% foreach my $dest_part_svc ( +% grep { $_->svcpart != $old_svcpart +% && $_->svcdb eq $src_part_svc->svcdb +% } +% qsearch('part_svc', { 'disabled' => '' } ) +% ) { +% + + <OPTION VALUE="<% $dest_part_svc->svcpart %>"><% $dest_part_svc->svcpart %>: <% $dest_part_svc->svc %> +% } + +</SELECT> +<BR> + +<BR> + +<SCRIPT TYPE="text/javascript"> +var confirm_change = '<P ALIGN="center"><B>Bulk customer service change - Are you sure?</B><BR><P ALIGN="CENTER" <INPUT TYPE="button" VALUE="Yes, make changes" onClick="process();"> <INPUT TYPE="BUTTON" VALUE="Cancel" onClick="cClick()">'; +</SCRIPT> + +<INPUT TYPE="button" VALUE="Bulk change customer services" onClick="overlib(confirm_change, CAPTION, 'Confirm bulk customer service change', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' );"> + +</FORM> + +</BODY> +</HTML> + + + diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi new file mode 100755 index 000000000..498d477cd --- /dev/null +++ b/httemplate/edit/cust_bill_pay.cgi @@ -0,0 +1,85 @@ +<% header("Apply Payment", '') %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_bill_pay.cgi" METHOD=POST> + +Payment #<B><% $paynum %></B> +<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>"> + +<BR>Date: <B><% time2str("%D", $cust_pay->_date) %></B> + +<BR>Amount: $<B><% $cust_pay->paid %></B> + +<BR>Unapplied amount: $<B><% $unapplied %></B> + +<SCRIPT TYPE="text/javascript"> +function changed(what) { + cust_bill = what.options[what.selectedIndex].value; + +% foreach my $cust_bill ( @cust_bill ) { + + if ( cust_bill == <% $cust_bill->invnum %> ) { + what.form.amount.value = "<% min($cust_bill->owed, $unapplied) %>"; + } + +% } + + if ( cust_bill == "Refund" ) { + what.form.amount.value = "<% $unapplied %>"; + } +} +</SCRIPT> + +<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)"> +<OPTION VALUE=""> + +% foreach my $cust_bill ( @cust_bill ) { + <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %> +% } + +<OPTION VALUE="Refund">Refund +</SELECT> + +<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8> + +<BR> +<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%init> +my($paynum, $amount, $invnum); +if ( $cgi->param('error') ) { + $paynum = $cgi->param('paynum'); + $amount = $cgi->param('amount'); + $invnum = $cgi->param('invnum'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $paynum = $1; + $amount = ''; + $invnum = ''; +} + +my $otaker = getotaker; + +my $p1 = popurl(1); + +my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); +die "payment $paynum not found!" unless $cust_pay; + +my $unapplied = $cust_pay->unapplied; + +my @cust_bill = sort { $a->_date <=> $b->_date + or $a->invnum <=> $b->invnum + } + grep { $_->owed != 0 } + qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } ); +</%init> + diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi new file mode 100755 index 000000000..13d062c74 --- /dev/null +++ b/httemplate/edit/cust_credit.cgi @@ -0,0 +1,80 @@ +<% include('/elements/header-popup.html', 'Enter Credit') %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_credit.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="crednum" VALUE=""> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="paybatch" VALUE=""> +<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>"> +<INPUT TYPE="hidden" NAME="credited" VALUE=""> +<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $otaker %>"> + +Credit +<% ntable("#cccccc", 2) %> + + <TR> + <TD ALIGN="right">Date</TD> + <TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD> + </TR> + + <TR> + <TD ALIGN="right">Amount</TD> + <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD> + </TR> + +% +%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!; +% + + <TR> + <TD ALIGN="right">Reason</TD> + <TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="<% $reason %>" SIZE=32></TD> + </TR> + + <TR> + <TD ALIGN="right">Auto-apply<BR>to invoices</TD> + <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD> + </TR> + +</TABLE> + +<BR> + +<CENTER><INPUT TYPE="submit" VALUE="Enter credit"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%once> +my $conf = new FS::Conf; +</%once> + +<%init> +my($custnum, $amount, $reason); +if ( $cgi->param('error') ) { + #$cust_credit = new FS::cust_credit ( { + # map { $_, scalar($cgi->param($_)) } fields('cust_credit') + #} ); + $custnum = $cgi->param('custnum'); + $amount = $cgi->param('amount'); + #$refund = $cgi->param('refund'); + $reason = $cgi->param('reason'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $custnum = $1; + $amount = ''; + #$refund = 'yes'; + $reason = ''; +} +my $_date = time; + +my $otaker = getotaker; + +my $p1 = popurl(1); +</%init> diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi new file mode 100755 index 000000000..249ba31d0 --- /dev/null +++ b/httemplate/edit/cust_credit_bill.cgi @@ -0,0 +1,92 @@ +<% header("Apply Credit", '') %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% $p1 %>process/cust_credit_bill.cgi" METHOD=POST> + +Credit #<B><% $crednum %></B> +<INPUT TYPE="hidden" NAME="crednum" VALUE="<% $crednum %>"> + +<BR>Date: <B><% time2str("%D", $cust_credit->_date) %></B> + +<BR>Amount: $<B><% $cust_credit->amount %></B> + +<BR>Unapplied amount: $<B><% $credited %></B> + +<BR>Reason: <B><% $cust_credit->reason %></B> + +<SCRIPT> +function changed(what) { + cust_bill = what.options[what.selectedIndex].value; + +% foreach my $cust_bill ( @cust_bill ) { + + if ( cust_bill == <% $cust_bill->invnum %> ) { + what.form.amount.value = "<% min($cust_bill->owed, $credited) %>"; + } + +% } + + if ( cust_bill == "Refund" ) { + what.form.amount.value = "<% $credited %>"; + } +} +</SCRIPT> + +<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)"> +<OPTION VALUE=""> + +% foreach my $cust_bill ( @cust_bill ) { + <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %> +% } + +<OPTION VALUE="Refund">Refund +</SELECT> + +<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8> + +<BR> +<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER> + +</FORM> +</BODY> +</HTML> + +<%init> +my($crednum, $amount, $invnum); +if ( $cgi->param('error') ) { + #$cust_credit_bill = new FS::cust_credit_bill ( { + # map { $_, scalar($cgi->param($_)) } fields('cust_credit_bill') + #} ); + $crednum = $cgi->param('crednum'); + $amount = $cgi->param('amount'); + #$refund = $cgi->param('refund'); + $invnum = $cgi->param('invnum'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $crednum = $1; + $amount = ''; + #$refund = 'yes'; + $invnum = ''; +} + +my $otaker = getotaker; + +my $p1 = popurl(1); + +my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); +die "credit $crednum not found!" unless $cust_credit; + +my $credited = $cust_credit->credited; + +my @cust_bill = sort { $a->_date <=> $b->_date + or $a->invnum <=> $b->invnum + } + grep { $_->owed != 0 } + qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } ); +</%init> + diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi new file mode 100755 index 000000000..57d4dac5e --- /dev/null +++ b/httemplate/edit/cust_main.cgi @@ -0,0 +1,486 @@ +% +% +% #for misplaced logic below +% #use FS::part_pkg; +% +% #for false laziness below (now more properly lazy) +% #use FS::svc_acct_pop; +% +% #for (other) false laziness below +% #use FS::agent; +% #use FS::type_pkgs; +% +%my $conf = new FS::Conf; +% +%#get record +% +%my $error = ''; +%my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart); +%my(@invoicing_list); +%my $same = ''; +%if ( $cgi->param('error') ) { +% $error = $cgi->param('error'); +% $cust_main = new FS::cust_main ( { +% map { $_, scalar($cgi->param($_)) } fields('cust_main') +% } ); +% $custnum = $cust_main->custnum; +% $saved_pkgpart = $cgi->param('pkgpart_svcpart') || ''; +% if ( $saved_pkgpart =~ /^(\d+)_/ ) { +% $saved_pkgpart = $1; +% } else { +% $saved_pkgpart = ''; +% } +% $username = $cgi->param('username'); +% $password = $cgi->param('_password'); +% $popnum = $cgi->param('popnum'); +% @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') ); +% $same = $cgi->param('same'); +% $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid'); +%} elsif ( $cgi->keywords ) { #editing +% my( $query ) = $cgi->keywords; +% $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 = ''; +% $popnum = 0; +% @invoicing_list = $cust_main->invoicing_list; +%} else { +% $custnum=''; +% $cust_main = new FS::cust_main ( {} ); +% $cust_main->otaker( &getotaker ); +% $cust_main->referral_custnum( $cgi->param('referral_custnum') ); +% $saved_pkgpart = 0; +% $username = ''; +% $password = ''; +% $popnum = 0; +% @invoicing_list = (); +%} +%$cgi->delete_all(); +% +%my $action = $custnum ? 'Edit' : 'Add'; +%$action .= ": ". $cust_main->name if $custnum; +% +%my $r = qq!<font color="#ff0000">*</font> !; +% +% + + +<!-- top --> + +<% include('/elements/header.html', + "Customer $action", + '', + ' onUnload="myclose()"' +) %> +% if ( $error ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT><BR><BR> +% } + + +<FORM NAME="topform" STYLE="margin-bottom: 0"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +% if ( $custnum ) { + + Customer #<B><% $custnum %></B> - + <B><FONT COLOR="<% $cust_main->statuscolor %>"> + <% ucfirst($cust_main->status) %> + </FONT></B> + <BR><BR> +% } + + +<% &ntable("#cccccc") %> + +<!-- agent --> + +<% include('/elements/tr-select-agent.html', $cust_main->agentnum, + 'label' => "<B>${r}Agent</B>", + 'empty_label' => 'Select agent', + ) +%> + +<!-- referral (advertising source) --> +% +%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0; +%if ( $custnum && ! $conf->exists('editreferrals') ) { +% + + + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>"> +% } else { + + + <% include('/elements/tr-select-part_referral.html', $refnum ) %> +% } + + +<!-- referring customer --> +% +%my $referring_cust_main = ''; +%if ( $cust_main->referral_custnum +% and $referring_cust_main = +% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) +%) { +% + + + <TR> + <TD ALIGN="right">Referring customer</TD> + <TD> + <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A> + </TD> + </TR> + <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>"> +% } elsif ( ! $conf->exists('disable_customer_referrals') ) { + + + <TR> + <TD ALIGN="right">Referring customer</TD> + <TD> + <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> --> + <% include('/elements/search-cust_main.html', + 'field_name' => 'referral_custnum', + ) + %> + </TD> + </TR> +% } else { + + + <INPUT TYPE="hidden" NAME="referral_custnum" VALUE=""> +% } + + +</TABLE> + +<!-- birthdate --> + +% if ( $conf->exists('cust_main-enable_birthdate') ) { + + <BR> + <% ntable("#cccccc", 2) %> + <% include ('/elements/tr-input-date-field.html', + 'birthdate', + $cust_main->birthdate, + 'Date of Birth', + $conf->config('date_format') || "%m/%d/%Y") + %> + + </TABLE> + +% } + +<!-- contact info --> + +<BR><BR> +Billing address +<% include('cust_main/contact.html', $cust_main, '', 'bill_changed(this)', '' ) %> + +<!-- service address --> +% if ( defined $cust_main->dbdef_table->column('ship_last') ) { + + +<SCRIPT> +function bill_changed(what) { + if ( what.form.same.checked ) { +% for (qw( last first company address1 address2 city zip daytime night fax )) { + + what.form.ship_<%$_%>.value = what.form.<%$_%>.value; +% } + + + what.form.ship_country.selectedIndex = what.form.country.selectedIndex; + function fix_ship_state() { + what.form.ship_state.selectedIndex = what.form.state.selectedIndex; + } + ship_country_changed(what.form.ship_country, fix_ship_state ); + + function fix_ship_county() { + what.form.ship_county.selectedIndex = what.form.county.selectedIndex; + } + ship_state_changed(what.form.ship_state, fix_ship_county ); + } +} +function samechanged(what) { + if ( what.checked ) { + bill_changed(what); +% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { + + what.form.ship_<%$_%>.disabled = true; + what.form.ship_<%$_%>.style.backgroundColor = '#dddddd'; +% } + + } else { +% for (qw( last first company address1 address2 city county state zip country daytime night fax )) { + + what.form.ship_<%$_%>.disabled = false; + what.form.ship_<%$_%>.style.backgroundColor = '#ffffff'; +% } + + } +} +</SCRIPT> +% +% my $checked = ''; +% my $disabled = ''; +% my $disabledselect = ''; +% unless ( $cust_main->ship_last && $same ne 'Y' ) { +% $checked = 'CHECKED'; +% $disabled = 'DISABLED style="background-color: #dddddd"'; +% foreach ( +% qw( last first company address1 address2 city county state zip country +% daytime night fax ) +% ) { +% $cust_main->set("ship_$_", $cust_main->get($_) ); +% } +% } +% + + +<BR> +Service address +(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$checked%>>same as billing address) +<% include('cust_main/contact.html', $cust_main, 'ship_', '', $disabled ) %> +% } + + +<!-- billing info --> + +<% include( 'cust_main/billing.html', $cust_main, + 'invoicing_list' => \@invoicing_list, + ) +%> + +<SCRIPT> +function bottomfixup(what) { + + var topvars = new Array( + 'birthdate', + + 'custnum', 'agentnum', 'refnum', 'referral_custnum', + + 'last', 'first', 'ss', 'company', + 'address1', 'address2', 'city', + 'county', 'state', 'zip', 'country', + 'daytime', 'night', 'fax', + + 'same', + + 'ship_last', 'ship_first', 'ship_company', + 'ship_address1', 'ship_address2', 'ship_city', + 'ship_county', 'ship_state', 'ship_zip', 'ship_country', + 'ship_daytime','ship_night', 'ship_fax', + + 'select' // XXX key + ); + + var layervars = new Array( + 'payauto', + 'payinfo', 'payinfo1', 'payinfo2', + 'payname', 'exp_month', 'exp_year', 'paycvv', + 'paystart_month', 'paystart_year', 'payissue', + 'payip', + 'paid' + ); + + var billing_bottomvars = new Array( + 'tax', + 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX', + 'spool_cdr' + ); + + for ( f=0; f < topvars.length; f++ ) { + var field = topvars[f]; + copyelement( document.topform.elements[field], + document.bottomform.elements[field] + ); + } + + var layerform = document.topform.select.options[document.topform.select.selectedIndex].value; + for ( f=0; f < layervars.length; f++ ) { + var field = layervars[f]; + copyelement( document.forms[layerform].elements[field], + document.bottomform.elements[field] + ); + } + + for ( f=0; f < billing_bottomvars.length; f++ ) { + var field = billing_bottomvars[f]; + copyelement( document.billing_bottomform.elements[field], + document.bottomform.elements[field] + ); + } + +} + +function copyelement(from, to) { + if ( from == undefined ) { + to.value = ''; + } else if ( from.type == 'select-one' ) { + to.value = from.options[from.selectedIndex].value; + //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value); + } else if ( from.type == 'checkbox' ) { + if ( from.checked ) { + to.value = from.value; + } else { + to.value = ''; + } + } else { + if ( from.value == undefined ) { + to.value = ''; + } else { + to.value = from.value; + } + } + //alert(from + " (" + from.type + "): " + to.name + " => " + to.value); +} + +</SCRIPT> + +<FORM ACTION="<% popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" onSubmit="document.bottomform.submit.disabled=true; bottomfixup(this.form);" STYLE="margin-top: 0; margin-bottom: 0"> +% foreach my $hidden ( +% 'birthdate', +% +% 'custnum', 'agentnum', 'refnum', 'referral_custnum', +% 'last', 'first', 'ss', 'company', +% 'address1', 'address2', 'city', +% 'county', 'state', 'zip', 'country', +% 'daytime', 'night', 'fax', +% +% 'same', +% +% 'ship_last', 'ship_first', 'ship_company', +% 'ship_address1', 'ship_address2', 'ship_city', +% 'ship_county', 'ship_state', 'ship_zip', 'ship_country', +% 'ship_daytime','ship_night', 'ship_fax', +% +% 'select', #XXX key +% +% 'payauto', +% 'payinfo', 'payinfo1', 'payinfo2', +% 'payname', 'exp_month', 'exp_year', 'paycvv', +% 'paystart_month', 'paystart_year', 'payissue', +% 'payip', +% 'paid', +% +% 'tax', +% 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX', +% 'spool_cdr' +% ) { +% + + <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE=""> +% } +% +% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly'; +% if (!$ro_comments || $cust_main->comments) { + +<BR>Comments +<% &ntable("#cccccc") %> + <TR> + <TD> + <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments" <%$ro_comments%>><% $cust_main->comments %></TEXTAREA> + </TD> + </TR> +</TABLE> +% +% } +% +%unless ( $custnum ) { +% # pry the wrong place for this logic. also pretty expensive +% #use FS::part_pkg; +% +% #false laziness, copied from FS::cust_pkg::order +% my $pkgpart; +% my @agents = $FS::CurrentUser::CurrentUser->agents; +% if ( scalar(@agents) == 1 ) { +% # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART +% $pkgpart = $agents[0]->pkgpart_hashref; +% } else { +% #can't know (agent not chosen), so, allow all +% my %typenum; +% foreach my $agent ( @agents ) { +% next if $typenum{$agent->typenum}++; +% #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref } +% foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround +% } +% } +% #eslaf +% +% my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } } +% qsearch( 'part_pkg', { 'disabled' => '' } ); +% +% if ( @part_pkg ) { +% +% # print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"), +% #apiabuse & undesirable wrapping +% +% + + <BR>First package + <% ntable("#cccccc") %> + + <TR> + <TD COLSPAN=2> + <SELECT NAME="pkgpart_svcpart"> + <OPTION VALUE="">(none) +% foreach my $part_pkg ( @part_pkg ) { + + + <OPTION VALUE="<% $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct') %>"<% ( $saved_pkgpart && $part_pkg->pkgpart == $saved_pkgpart ) ? ' SELECTED' : '' %>><% $part_pkg->pkg. " - ". $part_pkg->comment %> +% } + + </SELECT> + </TD> + </TR> +% +% #false laziness: (mostly) copied from edit/svc_acct.cgi +% #$ulen = $svc_acct->dbdef_table->column('username')->length; +% my $ulen = dbdef->table('svc_acct')->column('username')->length; +% my $ulen2 = $ulen+2; +% my $passwordmax = $conf->config('passwordmax') || 8; +% my $pmax2 = $passwordmax + 2; +% + + + <TR> + <TD ALIGN="right">Username</TD> + <TD> + <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Password</TD> + <TD> + <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $passwordmax %>> + (blank to generate) + </TD> + </TR> + + <TR> + <TD ALIGN="right">Access number</TD> + <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> + </TR> + </TABLE> +% } +% } + + +<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>"> +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="<% $custnum ? "Apply Changes" : "Add Customer" %>"> +<BR> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html new file mode 100644 index 000000000..78a2002a4 --- /dev/null +++ b/httemplate/edit/cust_main/billing.html @@ -0,0 +1,454 @@ +% +% +%my( $cust_main, %options ) = @_; +%my @invoicing_list = @{ $options{'invoicing_list'} }; +%my $conf = new FS::Conf; +%my $payby_default = $conf->config('payby-default'); +% +%my @payby = grep /\w/, $conf->config('payby'); +%#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +%@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) +% unless @payby; +% +%if ( $payby_default eq 'HIDE' ) { +% +% $cust_main->payby('BILL') unless $cust_main->payby; +% +% + + + <INPUT TYPE="hidden" NAME="select" VALUE="<% $cust_main->payby %>"> + + </FORM> + + <FORM NAME="<% $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0"> +% # XXX key +% foreach my $field (qw( payinfo payname paycvv paystart_month paystart_year payissue payip )) { + + + <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $cust_main->getfield($field) %>"> +% } +% +% #false laziness w/elements/select-month_year.html & view/cust_main/billing.html +% my( $mon, $year ); +% my $date = $cust_main->paydate || '12-2037'; +% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +% } else { +% die "unrecognized expiration date format: $date"; +% } +% + + + <INPUT TYPE="hidden" NAME="exp_month" VALUE="<% $mon %>"> + <INPUT TYPE="hidden" NAME="exp_year" VALUE="<% $year %>"> + + </FORM> + + <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0"> + + <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>"> + + <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>"> + + </FORM> +% } else { +% +% my $r = qq!<font color="#ff0000">*</font> !; +% +% + + + <BR>Billing information + <% &ntable("#cccccc") %> + + <TR> + <TD ALIGN="right" WIDTH="200"><%$r%>Billing type</TD> + + <SCRIPT> + + var mywindow = -1; + function myopen(filename,windowname,properties) { + myclose(); + mywindow = window.open(filename,windowname,properties); + } + function myclose() { + if ( mywindow != -1 ) + mywindow.close(); + mywindow = -1; + } + + var achwindow = -1; + function achopen(filename,windowname,properties) { + achclose(); + achwindow = window.open(filename,windowname,properties); + } + function achclose() { + if ( achwindow != -1 ) + achwindow.close(); + achwindow = -1; + } + + function card_changed(what) { + if ( + what.form.payinfo.value.substring(0, 4) == '4093' + || what.form.payinfo.value.substring(0, 4) == '4911' + || what.form.payinfo.value.substring(0, 4) == '4936' + || what.form.payinfo.value.substring(0, 6) == '564132' + || what.form.payinfo.value.substring(0, 2) == '63' + || what.form.payinfo.value.substring(0, 2) == '67' + ) + { + what.form.paystart_month.disabled = false; + what.form.paystart_year.disabled = false; + what.form.payissue.disabled = false; + what.form.paystart_month.style.backgroundColor = '#ffffff'; + what.form.paystart_year.style.backgroundColor = '#ffffff'; + what.form.payissue.style.backgroundColor = '#ffffff'; + document.getElementById('paystart_label').style.color = '#000000'; + document.getElementById('payissue_label').style.color = '#000000'; + } else { + what.form.paystart_month.disabled = true; + what.form.paystart_year.disabled = true; + what.form.payissue.disabled = true; + what.form.paystart_month.style.backgroundColor = '#dddddd'; + what.form.paystart_year.style.backgroundColor = '#dddddd'; + what.form.payissue.style.backgroundColor = '#dddddd'; + document.getElementById('paystart_label').style.color = '#999999'; + document.getElementById('payissue_label').style.color = '#999999'; + } + return true; + } + + </SCRIPT> + + <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> + <SCRIPT TYPE="text/javascript"> + function OLiframeContent(src, width, height, name) { + return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"' + +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">' + +'<div>[iframe not supported]</div></iframe>'); + } + </SCRIPT> +% +% +% my($payby, $payinfo, $payname)=( +% $cust_main->payby, +% $cust_main->payinfo, +% $cust_main->payname, +% ); +% my( $account, $aba ) = split('@', $payinfo); +% +% my $disabled = 'DISABLED style="background-color: #dddddd"'; +% my $text_disabled = 'style="color: #999999"'; +% if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) { +% $disabled = 'style="background-color: #ffffff"'; +% $text_disabled = 'style="color: #000000";' +% } +% +% my %payby = ( +% +% 'CARD' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'exp', +% 'selected_date' => +% ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ), +% ). +% +% '</TD></TR>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">CVV2 !. +% +% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. +% qq!</TD>!. +% '<TD WIDTH="408"><INPUT TYPE="text" NAME="paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'. +% +% +% qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'paystart', +% 'disabled' => $disabled, +% 'empty_option' => 1, +% 'start_year' => 2000, +% 'end_year' => (localtime())[5] + 1900, +% 'selected_date' => ( +% ( $payby =~ /^(CARD|DCRD)$/ +% && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) +% ? $cust_main->paystart_month. '-'. +% $cust_main->paystart_year +% : '' +% ) +% ). +% +% qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!. +% '<INPUT TYPE="text" NAME="payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'. +% +% '</TABLE>', +% +% 'CHEK' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD></TR>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !. +% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!. +% qq!</TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'LECB' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% qq!<INPUT TYPE="hidden" NAME="payname" VALUE="">!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'BILL' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!. +% +% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!. +% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'COMP' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE=""></TD></TR>!. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!. +% '<TD WIDTH="408">'. +% +% include('/elements/select-month_year.html', +% 'prefix' => 'exp', +% 'selected_date' => +% ( $payby eq 'COMP' ? $cust_main->paydate : '' ), +% ). +% +% '</TD></TR>'. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'CASH' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'WEST' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% 'MCRD' => +% +% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'. +% +% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!. +% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!. +% +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% '<TR><TD> </TD></TR>'. +% +% '</TABLE>', +% +% ); +% +% #this should use FS::payby +% my %allopt = ( +% 'CARD' => 'Credit card', +% 'CHEK' => 'Electronic check', +% 'LECB' => 'Phone bill billing', +% 'BILL' => 'Billing', +% 'CASH' => 'Cash', # initial payment, then billing', +% 'WEST' => 'Western Union', # initial payment, then billing', +% 'MCRD' => 'Manual credit card', # initial payment, then billing', +% 'COMP' => 'Complimentary', +% ); +% if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types +% # when editing customer +% delete $allopt{$_} for qw(CASH WEST MCRD); +% } +% +% tie my %options, 'Tie::IxHash', +% map { $_ => $allopt{$_} } +% grep { exists $allopt{$_} } +% @payby; +% +% my %payby2option = ( +% ( map { $_ => $_ } keys %options ), +% 'DCRD' => 'CARD', +% 'DCHK' => 'CHEK', +% ); +% +% my $widget = new HTML::Widgets::SelectLayers( +% 'options' => \%options, +% #'form_name' => 'dummy', +% #'form_action' => 'nothingyet', +% #chops bottom of page in IE# 'under_position' => 'absolute', +% 'html_between' => '</TD></TR></TABLE>', +% 'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] }, +% 'layer_callback' => sub { my $layer = shift; $payby{$layer}; }, +% ); +% +% + + + <TD WIDTH="408"><% $widget->html %> + + <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0"> + + <% &ntable("#cccccc") %> + + <TR><TD> </TD></TR> + + <TR> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</TD> + </TR> + + <TR> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <% + + ( ( ! @invoicing_list + && ! $conf->exists('disablepostalinvoicedefault') + && ! $cust_main->custnum + ) + || grep { $_ eq 'POST' } @invoicing_list ) + + ? 'CHECKED' + : '' + + %>> Postal mail invoice + + </TD> + </TR> + + <TR> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <% + + ( grep { $_ eq 'FAX' } @invoicing_list ) + ? 'CHECKED' + : '' + + %>> Fax invoice + + </TD> + </TR> + + <TR> + <TD ALIGN="right" WIDTH="200">Email invoice </TD> + <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD> + </TR> +% if ( $conf->exists('voip-cust_cdr_spools') ) { + + <TR> + <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD> + </TR> +% } else { + + <INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>"> +% } + + + </TABLE> + + </FORM> + + <% $r %> required fields +% } + + diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html new file mode 100644 index 000000000..a001634a2 --- /dev/null +++ b/httemplate/edit/cust_main/contact.html @@ -0,0 +1,128 @@ +% +% +%my( $cust_main, $pre, $onchange, $disabled ) = @_; +%my $conf = new FS::Conf; +% +%#false laziness with ship state +%my $countrydefault = $conf->config('countrydefault') || 'US'; +%$cust_main->set($pre.'country', $countrydefault ) +% unless $cust_main->get($pre.'country'); +% +%my $statedefault = $conf->config('statedefault') +% || ($countrydefault eq 'US' ? 'CA' : ''); +%$cust_main->set($pre.'state', $statedefault ) +% unless $cust_main->get($pre.'state') +% || $cust_main->get($pre.'country') ne $countrydefault; +% +%#my($county_html, $state_html, $country_html) = +%# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'), +%# $cust_main->get($pre.'state'), +%# $cust_main->get($pre.'country'), +%# $pre, +%# $onchange, +%# $disabled, +%# ); +% +%my %select_hash = ( +% 'county' => $cust_main->get($pre.'county'), +% 'state' => $cust_main->get($pre.'state'), +% 'country' => $cust_main->get($pre.'country'), +% 'prefix' => $pre, +% 'onchange' => $onchange, +% 'disabled' => $disabled, +%); +% +%my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone'; +%my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone'; +% +%my $r = qq!<font color="#ff0000">*</font> !; +% +% + + +<% &ntable("#cccccc") %> + +<TR> + <TH ALIGN="right"><%$r%>Contact name<BR>(last, first)</TH> + <TD COLSPAN=3> + <INPUT TYPE="text" NAME="<%$pre%>last" VALUE="<% $cust_main->get($pre.'last') %>" onChange="<% $onchange %>" <%$disabled%>> , + <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') %>" onChange="<% $onchange %>" <%$disabled%>> + </TD> +% if ( $conf->exists('show_ss') && !$pre ) { + + <TD ALIGN="right">SS#</TD> + <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $cust_main->ss %>" SIZE=11></TD> +% } elsif ( !$pre ) { + + <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $cust_main->ss %>"></TD> +% } + + +</TR> + +<TR> + <TD ALIGN="right">Company</TD> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TH ALIGN="right"><%$r%>Address</TH> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>address1" VALUE="<% $cust_main->get($pre.'address1') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TD ALIGN="right"> </TD> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>address2" VALUE="<% $cust_main->get($pre.'address2') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TH ALIGN="right"><%$r%>City</TH> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>city" VALUE="<% $cust_main->get($pre.'city') %>" onChange="<% $onchange %>" <%$disabled%>> + </TD> + <TH ALIGN="right"><%$r%>State</TH> + <TD> + <% include('select-county.html', %select_hash ) %> + <% include('select-state.html', %select_hash ) %> + </TD> + <TH><%$r%>Zip</TH> + <TD> + <INPUT TYPE="text" NAME="<%$pre%>zip" VALUE="<% $cust_main->get($pre.'zip') %>" SIZE=10 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TH ALIGN="right"><%$r%>Country</TH> + <TD COLSPAN=5><% include('select-country.html', %select_hash ) %></TD> +</TR> + +<TR> + <TD ALIGN="right"><% $daytime_label %></TD> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TD ALIGN="right"><% $night_label %></TD> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +<TR> + <TD ALIGN="right">Fax</TD> + <TD COLSPAN=5> + <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%>> + </TD> +</TR> + +</TABLE> +<%$r%>required fields<BR> + diff --git a/httemplate/edit/cust_main/select-country.html b/httemplate/edit/cust_main/select-country.html new file mode 100644 index 000000000..137f61975 --- /dev/null +++ b/httemplate/edit/cust_main/select-country.html @@ -0,0 +1,76 @@ + +<% include('/elements/xmlhttp.html', + 'url' => $p.'misc/states.cgi', + 'subs' => [ $opt{'prefix'}. 'get_states' ], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + function opt(what,value,text) { + var optionName = new Option(text, value, false, false); + var length = what.length; + what.options[length] = optionName; + } + + function <% $opt{'prefix'} %>country_changed(what, callback) { + + country = what.options[what.selectedIndex].value; + + function <% $opt{'prefix'} %>update_states(states) { + + // blank the current state list + for ( var i = what.form.<% $opt{'prefix'} %>state.length; i >= 0; i-- ) + what.form.<% $opt{'prefix'} %>state.options[i] = null; + + // add the new states + var statesArray = eval('(' + states + ')' ); + for ( var s = 0; s < statesArray.length; s=s+2 ) { + var stateLabel = statesArray[s+1]; + if ( stateLabel == "" ) + stateLabel = '(n/a)'; + opt(what.form.<% $opt{'prefix'} %>state, statesArray[s], stateLabel); + } + + //run the callback + if ( callback != null ) + callback(); + } + + // go get the new states + <% $opt{'prefix'} %>get_states( country, <% $opt{'prefix'} %>update_states ); + + } + +</SCRIPT> + +<SELECT NAME="<% $opt{'prefix'} %>country" onChange="<% $opt{'prefix'} %>country_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> + +% foreach my $country ( +% sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) +% or code2country($a) cmp code2country($b) } +% map { $_->country } +% qsearch({ +% 'select' => 'country', +% 'table' => 'cust_main_county', +% 'hashref' => {}, +% 'extra_sql' => 'GROUP BY country', +% }) +% ) { + + <OPTION VALUE="<% $country %>"<% $country eq $opt{'country'} ? ' SELECTED' : '' %>><% code2country($country). " ($country)" %> + +% } + +</SELECT> + +<%init> +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} + +my $conf = new FS::Conf; +my $countrydefault = $conf->config('countrydefault') || 'US'; +</%init> + diff --git a/httemplate/edit/cust_main/select-county.html b/httemplate/edit/cust_main/select-county.html new file mode 100644 index 000000000..c9b64bf4f --- /dev/null +++ b/httemplate/edit/cust_main/select-county.html @@ -0,0 +1,88 @@ +% if ( $countyflag ) { + + <% include('/elements/xmlhttp.html', + 'url' => $p.'misc/counties.cgi', + 'subs' => [ $opt{'prefix'}. 'get_counties' ], + ) + %> + + <SCRIPT TYPE="text/javascript"> + + function opt(what,value,text) { + var optionName = new Option(text, value, false, false); + var length = what.length; + what.options[length] = optionName; + } + + function <% $opt{'prefix'} %>state_changed(what, callback) { + + state = what.options[what.selectedIndex].text; + country = what.form.<% $opt{'prefix'} %>country.options[what.form.<% $opt{'prefix'} %>country.selectedIndex].text; + + function <% $opt{'prefix'} %>update_counties(counties) { + + // blank the current county list + for ( var i = what.form.<% $opt{'prefix'} %>county.length; i >= 0; i-- ) + what.form.<% $opt{'prefix'} %>county.options[i] = null; + + // add the new counties + var countiesArray = eval('(' + counties + ')' ); + for ( var s = 0; s < countiesArray.length; s++ ) { + var countyLabel = countiesArray[s]; + if ( countyLabel == "" ) + countyLabel = '(n/a)'; + opt(what.form.<% $opt{'prefix'} %>county, countiesArray[s], countyLabel); + } + + //run the callback + if ( callback != null ) + callback(); + } + + // go get the new counties + <% $opt{'prefix'} %>get_counties( state, country, <% $opt{'prefix'} %>update_counties ); + + } + + </SCRIPT> + + <SELECT NAME="<% $opt{'prefix'} %>county" onChange="<% $opt{'onchange'} %>" <% $opt{'disabled'} %>> +% foreach my $county ( +% sort +% map { $_->county } +% qsearch('cust_main_county', { 'state' => $opt{'state'}, +% 'country' => $opt{'country'}, +% } +% ) +% ) { + + <OPTION VALUE="<% $county %>"<% $county eq $opt{'county'} ? ' SELECTED' : '' %>><% $county %> + +% } + + </SELECT> + +% } else { + + + <SCRIPT TYPE="text/javascript"> + function <% $opt{'prefix'} %>state_changed(what) { + } + </SCRIPT> + + <INPUT TYPE="hidden" NAME="<% $opt{'prefix'} %>county" VALUE="<% $opt{'county'} %>"> + +% } + +<%init> +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} + +my $sql = "SELECT COUNT(*) FROM cust_main_county". + " WHERE county IS NOT NULL AND county != ''"; +my $sth = dbh->prepare($sql) or die dbh->errstr; +$sth->execute or die $sth->errstr; +my $countyflag = $sth->fetchrow_arrayref->[0]; +</%init> diff --git a/httemplate/edit/cust_main/select-state.html b/httemplate/edit/cust_main/select-state.html new file mode 100644 index 000000000..87546e5e3 --- /dev/null +++ b/httemplate/edit/cust_main/select-state.html @@ -0,0 +1,20 @@ +<SELECT NAME="<% $opt{'prefix'} %>state" onChange="<% $opt{'prefix'} %>state_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> + +% foreach my $state ( keys %states ) { + + <OPTION VALUE="<% $state %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' %> + +% } + + +</SELECT> + +<%init> +my %opt = @_; +foreach my $opt (qw( county state country prefix onchange disabled )) { + $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_}); +} + +tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} ); +</%init> + diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi new file mode 100755 index 000000000..01f0c1e0c --- /dev/null +++ b/httemplate/edit/cust_main_county-expand.cgi @@ -0,0 +1,55 @@ +<!-- mason kludge --> +% +% +%my($taxnum, $delim, $expansion, $taxclass ); +%my($query) = $cgi->keywords; +%if ( $cgi->param('error') ) { +% $taxnum = $cgi->param('taxnum'); +% $delim = $cgi->param('delim'); +% $expansion = $cgi->param('expansion'); +% $taxclass = $cgi->param('taxclass'); +%} else { +% $query =~ /^(taxclass)?(\d+)$/ +% or die "Illegal taxnum (query $query)"; +% $taxclass = $1 ? 'taxclass' : ''; +% $taxnum = $2; +% $delim = 'n'; +% $expansion = ''; +%} +% +%my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +% or die "cust_main_county.taxnum $taxnum not found"; +%die "Can't expand entry!" if $cust_main_county->getfield('county'); +% +%my $p1 = popurl(1); +%print header("Tax Rate (expand)", menubar( +% 'Main Menu' => popurl(2), +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print <<END; +% <FORM ACTION="${p1}process/cust_main_county-expand.cgi" METHOD=POST> +% <INPUT TYPE="hidden" NAME="taxnum" VALUE="$taxnum"> +% <INPUT TYPE="hidden" NAME="taxclass" VALUE="$taxclass"> +% Separate by +%END +%print '<INPUT TYPE="radio" NAME="delim" VALUE="n"'; +%print ' CHECKED' if $delim eq 'n'; +%print '>line (broken on some browsers) or', +% '<INPUT TYPE="radio" NAME="delim" VALUE="s"'; +%print ' CHECKED' if $delim eq 's'; +%print '>whitespace.'; +%print <<END; +% <BR><INPUT TYPE="submit" VALUE="Submit"> +% <BR><TEXTAREA NAME="expansion" ROWS=100>$expansion</TEXTAREA> +% </FORM> +% </CENTER> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi new file mode 100755 index 000000000..7d1354d3e --- /dev/null +++ b/httemplate/edit/cust_main_county.cgi @@ -0,0 +1,99 @@ +<!-- mason kludge --> +% +% +%print header("Edit tax rates", menubar( +% 'Main Menu' => popurl(2), +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="!, popurl(1), +% qq!process/cust_main_county.cgi" METHOD=POST>!, &table(), <<END; +% <TR> +% <TH><FONT SIZE=-1>Country</FONT></TH> +% <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> +%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> +%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 +% } qsearch('cust_main_county',{}) ) { +% my($hashref)=$cust_main_county->hashref; +% print <<END; +% <TR> +% <TD BGCOLOR="#ffffff">$hashref->{country}</TD> +%END +% +% print "<TD", $hashref->{state} +% ? ' BGCOLOR="#ffffff">'.$hashref->{state} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print "<TD", $hashref->{county} +% ? ' BGCOLOR="#ffffff">'. $hashref->{county} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print "<TD", $hashref->{taxclass} +% ? ' BGCOLOR="#ffffff">'. $hashref->{taxclass} +% : ' BGCOLOR="#cccccc">(ALL)' +% , "</TD>"; +% +% print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum}, +% 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>'; +% +%} +% +%print <<END; +% </TABLE> +% <INPUT TYPE="submit" VALUE="Apply changes"> +% </FORM> +% </CENTER> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/edit/cust_main_note.cgi b/httemplate/edit/cust_main_note.cgi new file mode 100755 index 000000000..468ef0051 --- /dev/null +++ b/httemplate/edit/cust_main_note.cgi @@ -0,0 +1,37 @@ +<% include('/elements/header-popup.html', 'Add Customer Note') %> + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<FORM ACTION="<% popurl(1) %>process/cust_main_note.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> + + +<BR><BR> +<TEXTAREA NAME="comment" ROWS="12" COLS="60"> +<% $comment %> +</TEXTAREA> + +<BR><BR> +<INPUT TYPE="submit" VALUE="Add note"> + +</FORM> +</BODY> +</HTML> + +<%init> +my($custnum, $comment); +$comment = ''; + +if ( $cgi->param('error') ) { + $comment = $cgi->param('comment'); +} +$cgi->param('custnum') =~ /^(\d+)$/; +$custnum = $1; + +die "illegal query ". $cgi->keywords unless $custnum; + +</%init> + diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi new file mode 100755 index 000000000..855fbfcf1 --- /dev/null +++ b/httemplate/edit/cust_pay.cgi @@ -0,0 +1,145 @@ +% if ( $link eq 'popup' ) { + <% include('/elements/header-popup.html', $title ) %> +% } else { + <% include("/elements/header.html", $title, '') %> +% } + +% if ( $cgi->param('error') ) { + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + +<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + +<FORM ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>"> +<INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>"> + +% unless ( $link eq 'popup' ) { + <% small_custview($custnum, $conf->config('countrydefault')) %> +% } + +<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> + +<BR><BR> +Payment +<% ntable("#cccccc", 2) %> + +<TR> + <TD ALIGN="right">Date</TD> + <TD COLSPAN=2> + <INPUT TYPE="text" NAME="_date" ID="_date_text" VALUE="<% time2str("%m/%d/%Y %r",$_date) %>"> + <IMG SRC="../images/calendar.png" ID="_date_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> +</TR> + +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "_date_text", + ifFormat: "%m/%d/%Y", + button: "_date_button", + align: "BR" + }); +</SCRIPT> + +<TR> + <TD ALIGN="right">Amount</TD> + <TD BGCOLOR="#ffffff" ALIGN="right"><% $money_char %></TD> + <TD><INPUT TYPE="text" NAME="paid" VALUE="<% $paid %>" SIZE=8 MAXLENGTH=8> by <B><% $payby{$payby} %></B></TD> +</TR> + +% if ( $payby eq 'BILL' ) { + <TR> + <TD ALIGN="right">Check #</TD> + <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD> + </TR> +% } + +<TR> +% if ( $link eq 'custnum' || $link eq 'popup' ) { + + <TD ALIGN="right">Auto-apply<BR>to invoices</TD> + <TD COLSPAN=2> + <SELECT NAME="apply"> + <OPTION VALUE="yes" SELECTED>yes + <OPTION>no</SELECT> + </TD> + +% } elsif ( $link eq 'invnum' ) { + + <TD ALIGN="right">Apply to</TD> + <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><% $linknum %></B> only</TD> + <INPUT TYPE="hidden" NAME="apply" VALUE="no"> + +% } +</TR> + +</TABLE> + +<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>"> + +<BR> +<INPUT TYPE="submit" VALUE="Post payment"> + +</FORM> +</BODY> +</HTML> + +<%once> +my $conf = new FS::Conf; + +my %payby = ( + 'BILL' => 'Check', + 'CASH' => 'Cash', + 'WEST' => 'Western Union', + 'MCRD' => 'Manual credit card', +); + +my $money_char = $conf->config('money_char') || '$'; +</%once> + +<%init> +my($link, $linknum, $paid, $payby, $payinfo, $_date); +if ( $cgi->param('error') ) { + $link = $cgi->param('link'); + $linknum = $cgi->param('linknum'); + $paid = $cgi->param('paid'); + $payby = $cgi->param('payby'); + $payinfo = $cgi->param('payinfo'); + $_date = $cgi->param('_date') ? str2time($cgi->param('_date')) : time; +} elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $link = $cgi->param('popup') ? 'popup' : 'custnum'; + $linknum = $1; + $paid = ''; + $payby = $cgi->param('payby') || 'BILL'; + $payinfo = ''; + $_date = time; +} elsif ( $cgi->param('invnum') =~ /^(\d+)$/ ) { + $link = 'invnum'; + $linknum = $1; + $paid = ''; + $payby = $cgi->param('payby') || 'BILL'; + $payinfo = ""; + $_date = time; +} else { + die "illegal query ". $cgi->keywords; +} + +my $paybatch = "webui-$_date-$$-". rand() * 2**32; + +my $title = 'Post '. $payby{$payby}. ' payment'; +$title .= " against Invoice #$linknum" if $link eq 'invnum'; + +my $custnum; +if ( $link eq 'invnum' ) { + my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } ) + or die "unknown invnum $linknum"; + $custnum = $cust_bill->custnum; +} elsif ( $link eq 'custnum' ) { + $custnum = $linknum; +} +</%init> + diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi new file mode 100755 index 000000000..7a0432c5d --- /dev/null +++ b/httemplate/edit/cust_pkg.cgi @@ -0,0 +1,152 @@ +% +% +%my %pkg = (); +%my %comment = (); +%my %all_pkg = (); +%my %all_comment = (); +%#foreach (qsearch('part_pkg', { 'disabled' => '' })) { +%# $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +%# $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +%#} +%foreach (qsearch('part_pkg', {} )) { +% $all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +% $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +% next if $_->disabled; +% $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +% $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +%} +% +%my($custnum, %remove_pkg); +%if ( $cgi->param('error') ) { +% $custnum = $cgi->param('custnum'); +% %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $custnum = $1; +% %remove_pkg = (); +%} +% +%my $p1 = popurl(1); +% +% +<% include('/elements/header.html', "Add/Edit Packages", '') %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% $p1 %>process/cust_pkg.cgi" METHOD=POST> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +% +%#current packages +%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); +% +%if (@cust_pkg) { +% + + + Current packages - select to remove (services are moved to a new package below) + <TABLE> + <TR STYLE="background-color: #cccccc;"> + <TH COLSPAN="2">Pkg #</TH> + <TH>Package description</TH> + </TR> + <BR><BR> +% +% +% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') } +% cmp $all_pkg{ $b->getfield('pkgpart') } +% } +% @cust_pkg +% ) +% { +% my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') ); +% my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : ''; +% +% + + + <TR> + <TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="<% $pkgnum %>"<% $checked %>></TD> + <TD ALIGN="right"><% $pkgnum %>:</TD> + <TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD> + </TR> +% } + + + </TABLE> + <BR><BR> +% } + + +Order new packages +<BR><BR> +% +%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +%my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); +% +%my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) } +% qsearch('type_pkgs',{'typenum'=> $agent->typenum }); +% +%my $count = 0; +%my $pkgparts = 0; +% + + +<TABLE> + <TR STYLE="background-color: #cccccc;"> + <TH>Qty.</TH> + <TH COLSPAN="2">Package Description</TH> + </TR> +% +%#foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { +%foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} } +% keys(%agent_pkgs) ) { +% $pkgparts++; +% next unless exists $pkg{$pkgpart}; #skip disabled ones +% #print qq!<TR>! if ( $count == 0 ); +% my $value = $cgi->param("pkg$pkgpart") || 0; +% + + + <TR> + <TD> + <INPUT TYPE="text" NAME="<% "pkg$pkgpart" %>" VALUE="<% $value %>" SIZE="2" MAXLENGTH="2"> + </TD> + <TD ALIGN="right"><% $pkgpart %>:</TD> + <TD><% $pkg{$pkgpart} %> - <% $comment{$pkgpart}%></TD> + </TR> +% +% $count ++ ; +% #if ( $count == 2 ) { +% # print qq!</TR>\n! ; +% # $count = 0; +% #} +%} +% + + +</TABLE> +% unless ( $pkgparts ) { +% my $p2 = popurl(2); +% my $typenum = $agent->typenum; +% my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); +% my $atype = $agent_type->atype; +% + + + (No <A HREF="<% $p2 %>browse/part_pkg.cgi">package definitions</A>, + or agent type + <A HREF="<% $p2 %>edit/agent_type.cgi?<% $typenum %>"><% $atype %></a> + is not allowed to purchase any packages.) +% } + + +<P><INPUT TYPE="submit" VALUE="Order"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi new file mode 100755 index 000000000..2b3e02614 --- /dev/null +++ b/httemplate/edit/cust_refund.cgi @@ -0,0 +1,95 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +%my $custnum = $cgi->param('custnum'); +%my $refund = $cgi->param('refund'); +%my $payby = $cgi->param('payby'); +%my $reason = $cgi->param('reason'); +% +%my( $paynum, $cust_pay ) = ( '', '' ); +%if ( $cgi->param('paynum') =~ /^(\d+)$/ ) { +% $paynum = $1; +% $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } ) +% or die "unknown payment # $paynum"; +% $refund ||= $cust_pay->unrefunded; +% if ( $custnum ) { +% die "payment # $paynum is not for specified customer # $custnum" +% unless $custnum == $cust_pay->custnum; +% } else { +% $custnum = $cust_pay->custnum; +% } +%} +%die "no custnum or paynum specified!" unless $custnum; +% +%my $_date = time; +% +%my $p1 = popurl(1); +% +%print header('Refund '. ucfirst(lc($payby)). ' payment', ''); +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +%print <<END, small_custview($custnum, $conf->config('countrydefault')); +% <FORM ACTION="${p1}process/cust_refund.cgi" METHOD=POST> +% <INPUT TYPE="hidden" NAME="refundnum" VALUE=""> +% <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum"> +% <INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum"> +% <INPUT TYPE="hidden" NAME="_date" VALUE="$_date"> +% <INPUT TYPE="hidden" NAME="payby" VALUE="$payby"> +% <INPUT TYPE="hidden" NAME="payinfo" VALUE=""> +% <INPUT TYPE="hidden" NAME="paybatch" VALUE=""> +% <INPUT TYPE="hidden" NAME="credited" VALUE=""> +% <BR> +%END +% +%if ( $cust_pay ) { +% +% #false laziness w/FS/FS/cust_pay.pm +% my $payby = $cust_pay->payby; +% my $payinfo = $cust_pay->payinfo; +% $payby =~ s/^BILL$/Check/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check/; +% $payinfo = $cust_pay->payinfo_masked if $payby eq 'CARD'; +% +% print '<BR>Payment'. ntable("#cccccc", 2). +% '<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$'. +% $cust_pay->paid. '</TD></TR>'. +% '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. +% time2str("%D",$cust_pay->_date). '</TD></TR>'. +% '<TR><TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff">'. +% ucfirst(lc($payby)). ' # '. $payinfo. '</TD></TR>'; +% #false laziness w/FS/FS/cust_main::realtime_refund_bop +% if ( $cust_pay->paybatch =~ /^(\w+):(\w+)(:(\w+))?$/ ) { +% my ( $processor, $auth, $order_number ) = ( $1, $2, $4 ); +% print '<TR><TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff">'. +% $processor. '</TD></TR>'; +% print '<TR><TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff">'. +% $auth. '</TD></TR>' +% if length($auth); +% print '<TR><TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff">'. +% $order_number. '</TD></TR>' +% if length($order_number); +% } +% print '</TABLE>'; +%} +% +%print '<BR>Refund'. ntable("#cccccc", 2). +% '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. +% time2str("%D",$_date). '</TD></TR>'; +% +%print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="refund" VALUE="$refund" SIZE=8 MAXLENGTH=8></TD></TR>!; +% +%print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!; +% +%print <<END; +%</TABLE> +%<BR> +%<INPUT TYPE="submit" VALUE="Post refund"> +% </FORM> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html new file mode 100644 index 000000000..ac00fc5f0 --- /dev/null +++ b/httemplate/edit/elements/edit.html @@ -0,0 +1,199 @@ +% +% +% # options example... +% # +% # 'name' => +% # 'table' => +% # #? 'primary_key' => #required when the dbdef doesn't know...??? +% # 'labels' => { +% # 'column' => 'Label', +% # } +% # +% # listref - each item is a literal column name (or method) or hashref +% # or (notyet) coderef +% # if not specified all columns (except for the primary key) will be editable +% # 'fields' => [ +% # 'columname', +% # { 'field' => 'another_columname', +% # 'type' => 'text', #text, fixed, hidden, checkbox +% # #eventually more for <SELECT>, etc. +% # 'value' => 'Y', #only for checkbox +% # }, +% # ] +% # +% # 'menubar' => '', #menubar arrayref +% # +% # #run when re-displaying with an error +% # 'error_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # #run when editing +% # 'edit_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # # returns a hashref for the new object +% # 'new_hashref_callback' +% # +% # #run when adding +% # 'new_callback' => sub { my( $cgi, $object ) = @_; }, +% # +% # #XXX describe +% # 'field_callback' => sub { }, +% # +% # #string or coderef of additional HTML to add before </TABLE> +% # 'html_table_bottom' => '', +% # +% # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' +% # +% # 'html_bottom' => '', #string +% # 'html_bottom' => sub { +% # my $object = shift; +% # # ... +% # "html_string"; +% # }, +% +% my(%opt) = @_; +% +% #false laziness w/process.html +% my $table = $opt{'table'}; +% my $class = "FS::$table"; +% my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || +% my $fields = $opt{'fields'} +% #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; +% || [ grep { $_ ne $pkey } fields($table) ]; +% #my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields; +% +% my $object; +% if ( $cgi->param('error') ) { +% +% $object = $class->new( { +% map { $_ => scalar($cgi->param($_)) } fields($table) +% }); +% +% &{$opt{'error_callback'}}($cgi, $object) +% if $opt{'error_callback'}; +% +% } elsif ( $cgi->keywords ) { #editing +% +% my( $query ) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $object = qsearchs( $table, { $pkey => $1 } ); +% warn "$table $pkey => $1" +% if $opt{'debug'}; +% +% &{$opt{'edit_callback'}}($cgi, $object) +% if $opt{'edit_callback'}; +% +% } else { #adding +% +% my $hashref = $opt{'new_hashref_callback'} +% ? &{$opt{'new_hashref_callback'}} +% : {}; +% +% $object = $class->new( $hashref ); +% +% &{$opt{'new_callback'}}($cgi, $object) +% if $opt{'new_callback'}; +% +% } +% +% my $action = $object->$pkey() ? 'Edit' : 'Add'; +% +% my $title = "$action $opt{'name'}"; +% +% my @menubar = (); +% if ( $opt{'menubar'} ) { +% @menubar = @{ $opt{'menubar'} }; +% } else { +% @menubar = ( +% 'Main menu' => $p, #eventually get rid of this when the ACL/UI update is done +% #eventually use Lingua::bs to pluralize +% "View all $opt{'name'}s" => $p. ( $opt{'viewall_dir'} || 'search' ). +% "/$table.html", +% ); +% } +% +% +<% include("/elements/header.html", $title, + include( '/elements/menubar.html', @menubar ) + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + + +<FORM ACTION="<% popurl(1) %>process/<% $table %>.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $object->$pkey() %>"> +<% ( $opt{labels} && exists $opt{labels}->{$pkey} ) + ? $opt{labels}->{$pkey} + : $pkey +%> +#<% $object->$pkey() || "(NEW)" %> + +<% ntable("#cccccc",2) %> +% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} } +% @$fields +% ) { +% +% &{ $opt{'field_callback'} }( $f ) +% if $opt{'field_callback'}; +% +% my $field = $f->{'field'}; +% my $type = $f->{'type'} ||= 'text'; +% +% + + + <TR> + + <TD ALIGN="right"> + <% ( $opt{labels} && exists $opt{labels}->{$field} ) + ? $opt{labels}->{$field} + : $field + %> + </TD> +% if ( $type eq 'fixed' ) { + + + <TD BGCOLOR="#dddddd"><% $f->{'value'} %></TD> + <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $f->{'value'} %>"> +% } elsif ( $type eq 'checkbox' ) { + + + <TD> + <INPUT TYPE="checkbox" NAME="<% $field %>" VALUE="<% $f->{'value'} %>" <% $object->$field() eq $f->{'value'} ? ' CHECKED' : '' %>> + </TD> +% } else { + + + <TD> + <INPUT TYPE="<% $type %>" NAME="<% $field %>" VALUE="<% $object->$field() %>"> + <TD> +% } + + + </TR> +% } + + +<% ref( $opt{'html_table_bottom'} ) + ? &{ $opt{'html_table_bottom'} }( $object ) + : $opt{'html_table_bottom'} +%> + +</TABLE> + +<% ref( $opt{'html_bottom'} ) + ? &{ $opt{'html_bottom'} }( $object ) + : $opt{'html_bottom'} +%> + +<BR> + +<INPUT TYPE="submit" VALUE="<% $object->$pkey() ? "Apply changes" : "Add $opt{'name'}" %>"> + +</FORM> + +<% include("/elements/footer.html") %> + diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html new file mode 100644 index 000000000..da59cc9ed --- /dev/null +++ b/httemplate/edit/elements/svc_Common.html @@ -0,0 +1,99 @@ +% +% +% my %opt = @_; +% +% #my( $svcnum, $pkgnum, $svcpart, $part_svc ); +% my( $pkgnum, $svcpart, $part_svc ); +% +% #get & untaint pkgnum & svcpart +% my($query) = $cgi->keywords; #they're not proper cgi params +% if ( $query =~ /^pkgnum(\d+)-svcpart(\d+)$/ ) { +% $pkgnum = $1; +% $svcpart = $2; +% $cgi->delete_all(); #so the standard edit.html treats this correctly as new +% } +% +% +<% include( 'edit.html', + + 'menubar' => [], + + 'error_callback' => sub { + my( $cgi, $svc_x ) = @_; + #$svcnum = $svc_x->svcnum; + $pkgnum = $cgi->param('pkgnum'); + $svcpart = $cgi->param('svcpart'); + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + }, + + 'edit_callback' => sub { + my( $cgi, $svc_x ) = @_; + #$svcnum = $svc_x->svcnum; + my $cust_svc = $svc_x->cust_svc + 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; + }, + + 'new_hash_callback' => sub { + #my( $cgi, $svc_x ) = @_; + + { svcpart => $svcpart }; + + }, + + 'new_callback' => sub { + my( $cgi, $svc_x ) = @_;; + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + + #$svcnum=''; + + $svc_x->set_default_and_fixed; + + }, + + 'field_callback' => sub { + my $f = shift; + my $columndef = $part_svc->part_svc_column($f->{'field'}); + my $flag = $columndef->columnflag; + if ( $flag eq 'F' ) { + $f->{'type'} = 'fixed'; + $f->{'value'} = $columndef->columnvalue; + } + }, + + 'html_table_bottom' => sub { + my $svc_x = shift; + my $html = ''; + foreach my $field ($svc_x->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. + $html .= + $svc_x->pvf($field)->widget( 'HTML', + 'edit', + $svc_x->getfield($field) + ); + } + } + $html; + }, + + 'html_bottom' => sub { + qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!. + qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; + }, + + 'debug' => 1, + + %opt #pass through/override params + ) +%> diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html new file mode 100644 index 000000000..beefcd580 --- /dev/null +++ b/httemplate/edit/inventory_class.html @@ -0,0 +1,10 @@ +<% include( 'elements/edit.html', + 'name' => 'Inventory Class', + 'table' => 'inventory_class', + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + }, + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/msgcat.cgi b/httemplate/edit/msgcat.cgi new file mode 100755 index 000000000..54a340d83 --- /dev/null +++ b/httemplate/edit/msgcat.cgi @@ -0,0 +1,59 @@ +<!-- mason kludge --> +% +% +%print header("Edit Message catalog", menubar( +%# 'Main Menu' => $p, +%)), '<BR>'; +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !. $cgi->param('error'). +% '</FONT><BR><BR>' +% if $cgi->param('error'); +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => 'en_US', +% 'options' => { 'en_US'=>'en_US' }, +% 'form_action' => 'process/msgcat.cgi', +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = qq!<INPUT TYPE="hidden" NAME="locale" VALUE="$layer">!. +% "<BR>Messages for locale $layer<BR>". table(). +% "<TR><TH COLSPAN=2>Code</TH>". +% "<TH>Message</TH>"; +% $html .= "<TH>en_US Message</TH>" unless $layer eq 'en_US'; +% $html .= '</TR>'; +% +% #foreach my $msgcat ( sort { $a->msgcode cmp $b->msgcode } +% # qsearch('msgcat', { 'locale' => $layer } ) ) { +% foreach my $msgcat ( qsearch('msgcat', { 'locale' => $layer } ) ) { +% $html .= +% '<TR><TD>'. $msgcat->msgnum. '</TD><TD>'. $msgcat->msgcode. '</TD>'. +% '<TD><INPUT TYPE="text" SIZE=32 '. +% qq! NAME="!. $msgcat->msgnum. '" '. +% qq!VALUE="!. ($cgi->param($msgcat->msgnum)||$msgcat->msg). qq!"></TD>!; +% unless ( $layer eq 'en_US' ) { +% my $en_msgcat = qsearchs('msgcat', { +% 'locale' => 'en_US', +% 'msgcode' => $msgcat->msgcode, +% } ); +% $html .= '<TD>'. $en_msgcat->msg. '</TD>'; +% } +% $html .= '</TR>'; +% } +% +% $html .= '</TABLE><BR><INPUT TYPE="submit" VALUE="Apply changes">'; +% +% $html; +% }, +% +%); +% +%print $widget->html; +% +%print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi new file mode 100755 index 000000000..da11fc774 --- /dev/null +++ b/httemplate/edit/part_bill_event.cgi @@ -0,0 +1,440 @@ +<!--mason kludge--> +% +% +%if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) { +% $cgi->param('eventpart', $1); +%} else { +% $cgi->param('eventpart', ''); +%} +% +%my ($query) = $cgi->keywords; +%my $action = ''; +%my $part_bill_event = ''; +%if ( $cgi->param('error') ) { +% $part_bill_event = new FS::part_bill_event ( { +% map { $_, scalar($cgi->param($_)) } fields('part_bill_event') +% } ); +%} +%if ( $query && $query =~ /^(\d+)$/ ) { +% $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1}); +%} else { +% $part_bill_event ||= new FS::part_bill_event {}; +%} +%$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add'; +%my $hashref = $part_bill_event->hashref; +% +% + + +<% include('/elements/header.html', + "$action Invoice Event Definition", + menubar( + 'Main Menu' => popurl(2), + 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi', + ) + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% popurl(1) %>process/part_bill_event.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="eventpart" VALUE="<% $part_bill_event->eventpart %>"> +Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %> + +<% ntable("#cccccc",2) %> + + <TR> + <TD ALIGN="right">Event name </TD> + <TD><INPUT TYPE="text" NAME="event" VALUE="<% $hashref->{event} %>"></TD> + </TR> + + <TR> + <TD ALIGN="right">For </TD> + <TD> + <SELECT NAME="payby"> +% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname; +% foreach my $payby ( keys %payby ) { +% + + + <OPTION VALUE="<% $payby %>"<% ($part_bill_event->payby eq $payby) ? ' SELECTED' : '' %>><% $payby{$payby} %></OPTION> +% } + + + </SELECT> customers + </TD> + </TR> +% my $days = $hashref->{seconds}/86400; + + + <TR> + <TD ALIGN="right">After</TD> + <TD><INPUT TYPE="text" NAME="days" VALUE="<% $days %>"> days</TD> + </TR> + + <TR> + <TD ALIGN="right">Test event</TD> + <TD> + <SELECT NAME="freq"> +% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly'; +% foreach my $freq ( keys %freq ) { +% + + + <OPTION VALUE="<% $freq %>"<% ($part_bill_event->freq eq $freq) ? ' SELECTED' : '' %>><% $freq{$freq} %></OPTION> +% } + + + </SELECT> + </TD> + </TR> + + + <TR> + <TD ALIGN="right">Disabled</TD> + <TD> + <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>> + </TD> + </TR> + + <TR> + <TD VALIGN="top" ALIGN="right">Action</TD> + <TD> +% +% +%#print ntable(); +% +%sub select_pkgpart { +% my $label = shift; +% my $plandata = shift; +% my %selected = map { $_=>1 } split(/,\s*/, $plandata->{$label}); +% qq(<SELECT NAME="$label" MULTIPLE>). +% join("\n", map { +% '<OPTION VALUE="'. $_->pkgpart. '"'. +% ( $selected{$_->pkgpart} ? ' SELECTED' : '' ). +% '>'. $_->pkg. ' - '. $_->comment +% } qsearch('part_pkg', { 'disabled' => '' } ) ). +% '</SELECT>'; +%} +% +%sub select_agentnum { +% my $plandata = shift; +% #my $agentnum = $plandata->{'agentnum'}; +% my %agentnums = map { $_=>1 } split(/,\s*/, $plandata->{'agentnum'}); +% '<SELECT NAME="agentnum" MULTIPLE>'. +% join("\n", map { +% '<OPTION VALUE="'. $_->agentnum. '"'. +% ( $agentnums{$_->agentnum} ? ' SELECTED' : '' ). +% '>'. $_->agent +% } qsearch('agent', { 'disabled' => '' } ) ). +% '</SELECT>'; +%} +% +%my $conf = new FS::Conf; +%my $money_char = $conf->config('money_char') || '$'; +% +%#this is pretty kludgy right here. +%tie my %events, 'Tie::IxHash', +% +% 'fee' => { +% 'name' => 'Late fee (flat)', +% 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\' );', +% 'html' => +% 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'. +% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +% 'weight' => 10, +% }, +% 'fee_percent' => { +% 'name' => 'Late fee (percentage)', +% 'code' => '$cust_main->charge( sprintf(\'%.2f\', $cust_bill->owed * %%%percent%%% / 100 ), \'%%%reason%%%\' );', +% 'html' => +% 'Percent <INPUT TYPE="text" SIZE="2" NAME="percent" VALUE="%%%percent%%%">%'. +% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +% 'weight' => 10, +% }, +% 'suspend' => { +% 'name' => 'Suspend', +% 'code' => '$cust_main->suspend();', +% 'weight' => 10, +% }, +% 'suspend-if-balance' => { +% 'name' => 'Suspend if balance (this invoice and previous) over', +% 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%% );', +% 'html' => " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%">', +% 'weight' => 10, +% }, +% 'suspend-if-pkgpart' => { +% 'name' => 'Suspend packages', +% 'code' => '$cust_main->suspend_if_pkgpart(%%%if_pkgpart%%%);', +% 'html' => sub { &select_pkgpart('if_pkgpart', @_) }, +% 'weight' => 10, +% }, +% 'suspend-unless-pkgpart' => { +% 'name' => 'Suspend packages except', +% 'code' => '$cust_main->suspend_unless_pkgpart(%%%unless_pkgpart%%%);', +% 'html' => sub { &select_pkgpart('unless_pkgpart', @_) }, +% 'weight' => 10, +% }, +% 'cancel' => { +% 'name' => 'Cancel', +% 'code' => '$cust_main->cancel();', +% 'weight' => 10, +% }, +% +% 'addpost' => { +% 'name' => 'Add postal invoicing', +% 'code' => '$cust_main->invoicing_list_addpost(); "";', +% 'weight' => 20, +% }, +% +% 'comp' => { +% 'name' => 'Pay invoice with a complimentary "payment"', +% 'code' => '$cust_bill->comp();', +% 'weight' => 30, +% }, +% +% 'realtime-card' => { +% 'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_card();', +% 'weight' => 30, +% }, +% +% 'realtime-check' => { +% 'name' => 'Run check with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_ach();', +% 'weight' => 30, +% }, +% +% 'realtime-lec' => { +% 'name' => 'Run phone bill ("LEC") billing with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +% 'code' => '$cust_bill->realtime_lec();', +% 'weight' => 30, +% }, +% +% 'batch-card' => { +% 'name' => 'Add card or check to a pending batch', +% 'code' => '$cust_bill->batch_card(%options);', +% 'weight' => 40, +% }, +% +% +% #'retriable' => { +% # 'name' => 'Mark batched card event as retriable', +% # 'code' => '$cust_pay_batch->retriable();', +% # 'weight' => 60, +% #}, +% +% 'send' => { +% 'name' => 'Send invoice (email/print/fax)', +% 'code' => '$cust_bill->send();', +% 'weight' => 50, +% }, +% +% 'send_alternate' => { +% 'name' => 'Send invoice (email/print/fax) with alternate template', +% 'code' => '$cust_bill->send(\'%%%templatename%%%\');', +% 'html' => +% '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">', +% 'weight' => 50, +% }, +% +% 'send_if_newest' => { +% 'name' => 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)', +% 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');', +% 'html' => +% '<INPUT TYPE="text" NAME="if_newest_templatename" VALUE="%%%if_newest_templatename%%%">', +% 'weight' => 50, +% }, +% +% 'send_agent' => { +% 'name' => 'Send invoice (email/print/fax) ', +% 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', [ %%%agentnum%%% ], \'%%%agent_invoice_from%%%\');', +% 'html' => sub { +% '<TABLE BORDER=0> +% <TR> +% <TD ALIGN="right">only for agent(s) </TD> +% <TD>'. &select_agentnum(@_). '</TD> +% </TR> +% <TR> +% <TD ALIGN="right">with template </TD> +% <TD> +% <INPUT TYPE="text" NAME="agent_templatename" VALUE="%%%agent_templatename%%%"> +% </TD> +% </TR> +% <TR> +% <TD ALIGN="right">email From: </TD> +% <TD> +% <INPUT TYPE="text" NAME="agent_invoice_from" VALUE="%%%agent_invoice_from%%%"> +% </TD> +% </TR> +% </TABLE>'; +% }, +% 'weight' => 50, +% }, +% +% 'send_csv_ftp' => { +% 'name' => 'Upload CSV invoice data to an FTP server', +% 'code' => '$cust_bill->send_csv( protocol => \'ftp\', +% server => \'%%%ftpserver%%%\', +% username => \'%%%ftpusername%%%\', +% password => \'%%%ftppassword%%%\', +% dir => \'%%%ftpdir%%%\', +% \'format\' => \'%%%ftpformat%%%\', +% );', +% 'html' => +% '<TABLE BORDER=0>'. +% '<TR><TD ALIGN="right">Format ("default" or "billco"): </TD>'. +% '<TD>'. +% '<!--'. +% '<SELECT NAME="ftpformat">'. +% '<OPTION VALUE="default">Default'. +% '<OPTION VALUE="billco">Billco'. +% '</SELECT>'. +% '-->'. +% '<INPUT TYPE="text" NAME="ftpformat" VALUE="%%%ftpformat%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP server: </TD>'. +% '<TD><INPUT TYPE="text" NAME="ftpserver" VALUE="%%%ftpserver%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP username: </TD><TD>'. +% '<INPUT TYPE="text" NAME="ftpusername" VALUE="%%%ftpusername%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP password: </TD><TD>'. +% '<INPUT TYPE="text" NAME="ftppassword" VALUE="%%%ftppassword%%%">'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">FTP directory: </TD>'. +% '<TD><INPUT TYPE="text" NAME="ftpdir" VALUE="%%%ftpdir%%%">'. +% '</TD></TR>'. +% '</TABLE>', +% 'weight' => 50, +% }, +% +% 'spool_csv' => { +% 'name' => 'Spool CSV invoice data', +% 'code' => '$cust_bill->spool_csv( +% \'format\' => \'%%%spoolformat%%%\', +% \'dest\' => \'%%%spooldest%%%\', +% \'balanceover\' => \'%%%spoolbalanceover%%%\', +% \'agent_spools\' => \'%%%spoolagent_spools%%%\', +% );', +% 'html' => sub { +% my $plandata = shift; +% +% my $html = +% '<TABLE BORDER=0>'. +% '<TR><TD ALIGN="right">Format: </TD>'. +% '<TD>'. +% '<SELECT NAME="spoolformat">'; +% +% foreach my $option (qw( default billco )) { +% $html .= qq(<OPTION VALUE="$option"); +% $html .= ' SELECTED' if $option eq $plandata->{'spoolformat'}; +% $html .= ">\u$option"; +% } +% +% $html .= +% '</SELECT>'. +% '</TD></TR>'. +% '<TR><TD ALIGN="right">For destination: </TD>'. +% '<TD>'. +% '<SELECT NAME="spooldest">'; +% +% tie my %dest, 'Tie::IxHash', +% '' => '(all)', +% 'POST' => 'Postal Mail', +% 'EMAIL' => 'Email', +% 'FAX' => 'Fax', +% ; +% +% foreach my $dest (keys %dest) { +% $html .= qq(<OPTION VALUE="$dest"); +% $html .= ' SELECTED' if $dest eq $plandata->{'spooldest'}; +% $html .= '>'. $dest{$dest}; +% } +% +% $html .= +% '</SELECT>'. +% '</TD></TR>'. +% +% '<TR>'. +% '<TD ALIGN="right">if balance (this invoice and previous) over </TD>'. +% '<TD>'. +% "$money_char ". +% '<INPUT TYPE="text" SIZE="7" NAME="spoolbalanceover" VALUE="%%%spoolbalanceover%%%">'. +% '</TD>'. +% '<TR><TD ALIGN="right">Individual per-agent spools? </TD>'. +% '<TD><INPUT TYPE="checkbox" NAME="spoolagent_spools" VALUE="1" '. +% ( $plandata->{'spoolagent_spools'} ? 'CHECKED' : '' ). +% '>'. +% '</TD></TR>'. +% '</TABLE>'; +% +% $html; +% }, +% 'weight' => 50, +% }, +% +% 'bill' => { +% 'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)', +% 'code' => '$cust_main->bill();', +% 'weight' => 60, +% }, +% +% 'apply' => { +% 'name' => 'Apply unapplied payments and credits', +% 'code' => '$cust_main->apply_payments; $cust_main->apply_credits; "";', +% 'weight' => 70, +% }, +% +% 'collect' => { +% 'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)', +% 'code' => '$cust_main->collect();', +% 'weight' => 80, +% }, +% +%; +% +%foreach my $event ( keys %events ) { +% my %plandata = map { /^(\w+) (.*)$/; ($1, $2); } +% split(/\n/, $part_bill_event->plandata); +% my $html = $events{$event}{html}; +% if ( ref($html) eq 'CODE' ) { +% $html = &{$html}(\%plandata); +% } +% while ( $html =~ /%%%(\w+)%%%/ ) { +% my $field = $1; +% $html =~ s/%%%$field%%%/$plandata{$field}/; +% } +% +% print ntable( "#cccccc", 2). +% qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !; +% print "CHECKED " if $event eq $part_bill_event->plan; +% print qq!VALUE="!. $event. ":". $events{$event}{weight}. ":". +% encode_entities($events{$event}{code}). +% qq!">$events{$event}{name}</TD>!; +% print '<TD>'. $html. '</TD>' if $html; +% print qq!</TR>!; +% print '</TABLE>'; +%} +% +%#print '</TABLE>'; +% +%print <<END; +%</TD></TR> +%</TABLE> +%END +% +%print qq!<INPUT TYPE="submit" VALUE="!, +% $hashref->{eventpart} ? "Apply changes" : "Add invoice event", +% qq!">!; +% + + + </FORM> + </BODY> +</HTML> + + diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi new file mode 100644 index 000000000..6717471dd --- /dev/null +++ b/httemplate/edit/part_export.cgi @@ -0,0 +1,130 @@ +<!-- mason kludge --> +% +% +%#if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { +%# $cgi->param('clone', $1); +%#} else { +%# $cgi->param('clone', ''); +%#} +% +%my($query) = $cgi->keywords; +%my $action = ''; +%my $part_export = ''; +%if ( $cgi->param('error') ) { +% $part_export = new FS::part_export ( { +% map { $_, scalar($cgi->param($_)) } fields('part_export') +% } ); +%} elsif ( $query =~ /^(\d+)$/ ) { +% $part_export = qsearchs('part_export', { 'exportnum' => $1 } ); +%} else { +% $part_export = new FS::part_export; +%} +%$action ||= $part_export->exportnum ? 'Edit' : 'Add'; +% +%#my $exports = FS::part_export::export_info($svcdb); +%my $exports = FS::part_export::export_info(); +% +%my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports; +%$layers{''}=''; +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => $part_export->exporttype, +% 'options' => \%layers, +% 'form_name' => 'dummy', +% 'form_action' => 'process/part_export.cgi', +% 'form_text' => [qw( exportnum machine )], +%# 'form_checkbox' => [qw()], +% 'html_between' => "</TD></TR></TABLE>\n", +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!. +% ntable("#cccccc",2); +% +% $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. +% $exports->{$layer}{notes}. '</TD></TR>' +% if $layer; +% +% foreach my $option ( keys %{$exports->{$layer}{options}} ) { +% my $optinfo = $exports->{$layer}{options}{$option}; +% die "Retreived non-ref export info option from $layer export: $optinfo" +% unless ref($optinfo); +% my $label = $optinfo->{label}; +% my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text'; +% my $value = $cgi->param($option) +% || ( $part_export->exportnum && $part_export->option($option) ) +% || ( (exists $optinfo->{default} && !$part_export->exportnum) +% ? $optinfo->{default} +% : '' +% ); +% $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!; +% if ( $type eq 'select' ) { +% $html .= qq!<SELECT NAME="$option">!; +% foreach my $select_option ( @{$optinfo->{options}} ) { +% #if ( ref($select_option) ) { +% #} else { +% my $selected = $select_option eq $value ? ' SELECTED' : ''; +% $html .= qq!<OPTION VALUE="$select_option"$selected>!. +% qq!$select_option</OPTION>!; +% #} +% } +% $html .= '</SELECT>'; +% } elsif ( $type eq 'textarea' ) { +% $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!. +% encode_entities($value). '</TEXTAREA>'; +% } elsif ( $type eq 'text' ) { +% $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; +% $html .= '>'; +% } else { +% $html .= "unknown type $type"; +% } +% $html .= '</TD></TR>'; +% } +% $html .= '</TABLE>'; +% +% $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'. +% join(',', keys %{$exports->{$layer}{options}} ). '">'; +% +% $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'. +% $exports->{$layer}{nodomain}. '">'; +% +% $html .= '<INPUT TYPE="submit" VALUE="'. +% ( $part_export->exportnum ? "Apply changes" : "Add export" ). +% '">'; +% +% $html; +% }, +%); +% +% + +<% include("/elements/header.html","$action Export", menubar( + 'Main Menu' => popurl(2), +), ' onLoad="visualize()"') +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + + +<FORM NAME="dummy"> +<INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>"> + +<% ntable("#cccccc",2) %> +<TR> + <TD ALIGN="right">Export host</TD> + <TD> + <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>"> + </TD> +</TR> +<TR> + <TD ALIGN="right">Export</TD> + <TD><% $widget->html %> +</BODY> +</HTML> + diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi new file mode 100755 index 000000000..185d66ac7 --- /dev/null +++ b/httemplate/edit/part_pkg.cgi @@ -0,0 +1,356 @@ +% +% +%if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { +% $cgi->param('clone', $1); +%} else { +% $cgi->param('clone', ''); +%} +%if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) { +% $cgi->param('pkgnum', $1); +%} else { +% $cgi->param('pkgnum', ''); +%} +% +%my ($query) = $cgi->keywords; +% +%my $part_pkg = ''; +%if ( $cgi->param('error') ) { +% $part_pkg = new FS::part_pkg ( { +% map { $_, scalar($cgi->param($_)) } fields('part_pkg') +% } ); +%} +% +%my $action = ''; +%my $clone_part_pkg = ''; +%my $pkgpart = ''; +%if ( $cgi->param('clone') ) { +% $pkgpart = $cgi->param('clone'); +% $action = 'Custom Pricing'; +% $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } ); +% $part_pkg ||= $clone_part_pkg->clone; +% $part_pkg->disabled('Y'); #isn't sticky on errors +%} elsif ( $query && $query =~ /^(\d+)$/ ) { +% $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1}); +% $pkgpart = $part_pkg->pkgpart; +%} else { +% unless ( $part_pkg ) { +% $part_pkg = new FS::part_pkg {}; +% $part_pkg->plan('flat'); +% } +%} +%unless ( $part_pkg->plan ) { #backwards-compat +% $part_pkg->plan('flat'); +% $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n". +% "recur_fee=". $part_pkg->recur. "\n"); +%} +%$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add'; +%my $hashref = $part_pkg->hashref; +% +% + + +<% include("/elements/header.html","$action Package Definition", menubar( + 'Main Menu' => popurl(2), + 'View all packages' => popurl(2). 'browse/part_pkg.cgi', +)) %> +% #), ' onLoad="visualize()"'); +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM NAME="dummy"> + +<% itable('',8,1) %><TR><TD VALIGN="top"> + +Package information + +<% ntable("#cccccc",2) %> + <TR> + <TD ALIGN="right">Package Definition #</TD> + <TD BGCOLOR="#ffffff"> + <% $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %> + </TD> + </TR> + <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> + <% include( '/elements/tr-select-pkg_class.html', $part_pkg->classnum ) %> + <TR> + <TD ALIGN="right">Promotional code</TD> + <TD> + <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%$part_pkg->promo_code%>"> + </TD> + </TR> + <TR> + <TD ALIGN="right">Disable new orders</TD> + <TD> + <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %> + </TD> + </TR> + +</TABLE> + +</TD><TD VALIGN="top"> + +Tax information +<% ntable("#cccccc", 2) %> + <TR> + <TD ALIGN="right">Setup fee tax exempt</TD> + <TD> + <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>> + </TD> + </TR> + <TR> + <TD ALIGN="right">Recurring fee tax exempt</TD> + <TD> + <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <% $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>> + </TD> + </TR> +% my $conf = new FS::Conf; +% if ( $conf->exists('enable_taxclasses') ) { + + + <TR> + <TD align="right">Tax class</TD> + <TD> + <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> + </TD> + </TR> +% } else { + + + <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %> +% } + + +</TABLE> + +</TD></TR></TABLE> +% +% +%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>'; +% +% + + +<BR><BR>Services included +<% itable('', 4, 1) %><TR><TD VALIGN="top"> +<% $thead %> +% +% +%my $where = "WHERE disabled IS NULL OR disabled = ''"; +%if ( $pkgpart ) { +% $where .= " OR 0 < ( SELECT quantity FROM pkg_svc +% WHERE pkg_svc.svcpart = part_svc.svcpart +% AND pkgpart = $pkgpart +% )"; +%} +%my @part_svc = qsearch('part_svc', {}, '', $where); +%my $q_part_pkg = $clone_part_pkg || $part_pkg; +%my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc; +% +%my @fixups = (); +%my $count = 0; +%my $columns = 3; +%foreach my $part_svc ( @part_svc ) { +% my $svcpart = $part_svc->svcpart; +% my $pkg_svc = $pkg_svc{$svcpart} +% || new FS::pkg_svc ( { +% 'pkgpart' => $pkgpart, +% 'svcpart' => $svcpart, +% 'quantity' => 0, +% 'primary_svc' => '', +% } ); +% +% push @fixups, "pkg_svc$svcpart"; +% +% + + + <TR> + <TD> + <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<% $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0 %>"> + </TD> + + <TD> + <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>> + </TD> + + <TD> + <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %> + </TD> + </TR> +% foreach ( 1 .. $columns-1 ) { +% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) { +% + + </TABLE></TD><TD VALIGN="top"><% $thead %> +% } +% } +% $count++; +% +% } + + +</TR></TABLE></TD></TR></TABLE> +% foreach my $f ( qw( clone pkgnum ) ) { + + <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cgi->param($f) %>"> +% } + +<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $part_pkg->pkgpart %>"> +% +% +%# prolly should be in database +%tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() }; +% +%my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } +% split("\n", ($clone_part_pkg||$part_pkg)->plandata ); +%#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n"; +% +%tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans; +% +%my @form_select = ('classnum'); +%if ( $conf->exists('enable_taxclasses') ) { +% push @form_select, 'taxclass'; +%} else { +% push @fixups, 'taxclass'; #hidden +%} +% +%my @form_radio = (); +%if ( dbdef->table('pkg_svc')->column('primary_svc') ) { +% push @form_radio, 'pkg_svc_primary'; +%} +% +%tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()}; +%if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) { +% delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq; +%} +% +%my $widget = new HTML::Widgets::SelectLayers( +% 'selected_layer' => $part_pkg->plan, +% 'options' => \%options, +% 'form_name' => 'dummy', +% 'form_action' => 'process/part_pkg.cgi', +% 'form_text' => [ qw(pkg comment promo_code clone pkgnum pkgpart), +% @fixups +% ], +% 'form_checkbox' => [ qw(setuptax recurtax disabled) ], +% 'form_radio' => \@form_radio, +% 'form_select' => \@form_select, +% 'layer_callback' => sub { +% my $layer = shift; +% my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!. +% ntable("#cccccc",2); +% $html .= ' +% <TR> +% <TD ALIGN="right">Recurring fee frequency </TD> +% <TD><SELECT NAME="freq"> +% '; +% +% my @freq = keys %freq; +% @freq = grep { /^\d+$/ } @freq +% if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm'; +% foreach my $freq ( @freq ) { +% $html .= qq(<OPTION VALUE="$freq"); +% $html .= ' SELECTED' if $freq eq $part_pkg->freq; +% $html .= ">$freq{$freq}"; +% } +% $html .= '</SELECT></TD></TR>'; +% +% my $href = $plans{$layer}->{'fields'}; +% foreach my $field ( exists($plans{$layer}->{'fieldorder'}) +% ? @{$plans{$layer}->{'fieldorder'}} +% : keys %{ $href } +% ) { +% +% $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>'; +% +% if ( ! exists($href->{$field}{'type'}) ) { +% $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!. +% ( exists($plandata{$field}) +% ? $plandata{$field} +% : $href->{$field}{'default'} ). +% qq!" onChange="fchanged(this)">!; +% } elsif ( $href->{$field}{'type'} eq 'checkbox' ) { +% $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !. +% ( exists($plandata{$field}) && $plandata{$field} +% ? ' CHECKED' +% : '' +% ). '>'; +% } elsif ( $href->{$field}{'type'} =~ /^select/ ) { +% $html .= '<SELECT'; +% $html .= ' MULTIPLE' +% if $href->{$field}{'type'} eq 'select_multiple'; +% $html .= qq! NAME="$field" onChange="fchanged(this)">!; +% +% if ( $href->{$field}{'select_table'} ) { +% foreach my $record ( +% qsearch( $href->{$field}{'select_table'}, +% $href->{$field}{'select_hash'} ) +% ) { +% my $value = $record->getfield($href->{$field}{'select_key'}); +% $html .= qq!<OPTION VALUE="$value"!. +% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/ +% ? ' SELECTED' +% : '' +% ). +% '>'. $record->getfield($href->{$field}{'select_label'}); +% } +% } elsif ( $href->{$field}{'select_options'} ) { +% foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) { +% my $value = $href->{$field}{'select_options'}{$key}; +% $html .= qq!<OPTION VALUE="$key"!. +% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/ +% ? ' SELECTED' +% : '' +% ). +% '>'. $value; +% } +% +% } else { +% $html .= '<font color="#ff0000">warning: '. +% "don't know how to retreive options for $field select field". +% '</font>'; +% } +% $html .= '</SELECT>'; +% } +% +% $html .= '</TD></TR>'; +% } +% $html .= '</TABLE>'; +% +% $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'. +% join(',', keys %{ $href } ). '">'. +% '<BR><BR>'; +% +% $html .= '<INPUT TYPE="submit" VALUE="'. +% ( $hashref->{pkgpart} ? "Apply changes" : "Add package" ). +% '" onClick="fchanged(this)">'; +% +% $html; +% +% }, +%); +% +% + + +<BR><BR>Price plan <% $widget->html %> + </BODY> +</HTML> diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html new file mode 100755 index 000000000..7ce52174d --- /dev/null +++ b/httemplate/edit/part_referral.html @@ -0,0 +1,9 @@ +<% include( 'elements/edit.html', + 'name' => 'Advertising source', + 'table' => 'part_referral', + 'fields' => [ 'referral' ], + 'labels' => { 'referral' => 'Advertising source' }, + 'viewall_dir' => 'browse', + 'html_table_bottom' => include('/elements/tr-select-agent.html'), + ) +%> diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi new file mode 100755 index 000000000..595d7b87a --- /dev/null +++ b/httemplate/edit/part_svc.cgi @@ -0,0 +1,456 @@ +% +%my $part_svc; +%my $clone = ''; +%if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone +% #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query"; +% $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } ) +% or die "unknown svcpart: $1"; +% $clone = $part_svc->svcpart; +% $part_svc->svcpart(''); +%} elsif ( $cgi->keywords ) { #edit +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "malformed query: $query"; +% $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } ) +% or die "unknown svcpart: $1"; +%} else { #adding +% $part_svc = new FS::part_svc {}; +%} +% +%my $action = $part_svc->svcpart ? 'Edit' : 'Add'; +%my $hashref = $part_svc->hashref; +%# my $p_svcdb = $part_svc->svcdb || 'svc_acct'; +% +% +% #" onLoad=\"visualize()\"" +% + +<% include("/elements/header.html","$action Service Definition", + menubar( 'Main Menu' => $p, + 'View all service definitions' => "${p}browse/part_svc.cgi" + ), + ) +%> + +<FORM NAME="dummy"> + + Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> +<BR><BR> +Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR> +Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>"> +<BR> +Service definitions are the templates for items you offer to your customers. +<UL><LI>svc_acct - Accounts - anything with a username (Mailboxes, PPP accounts, shell accounts, RADIUS entries for broadband, etc.) + <LI>svc_domain - Domains + <LI>svc_forward - mail forwarding + <LI>svc_www - Virtual domain website + <LI>svc_broadband - Broadband/High-speed Internet service (always-on) + <LI>svc_phone - Customer phone numbers + <LI>svc_external - Externally-tracked service +<!-- <LI>svc_charge - One-time charges (Partially unimplemented) + <LI>svc_wo - Work orders (Partially unimplemented) +--> +</UL> +For the selected table, you can give fields default or fixed (unchangable) +values, or select an inventory class to manually or automatically fill in +that field. +<BR><BR> +% +% +%#these might belong somewhere else for other user interfaces +%#pry need to eventually create stuff that's shared amount UIs +%my $conf = new FS::Conf; +%my %defs = ( +% +% 'svc_acct' => { +% 'dir' => 'Home directory', +% 'uid' => 'UID (set to fixed and blank for no UIDs)', +% 'slipip' => 'IP address', +%# 'popnum' => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, +% 'popnum' => { +% desc => 'Access number', +% type => 'select', +% select_table => 'svc_acct_pop', +% select_key => 'popnum', +% select_label => 'city', +% }, +% 'username' => { +% desc => 'Username', +% type => 'text', +% disable_default => 1, +% disable_fixed => 1, +% }, +% 'quota' => { +% desc => '', +% type => 'text', +% disable_inventory => 1, +% }, +% '_password' => 'Password', +% 'gid' => 'GID (when blank, defaults to UID)', +% 'shell' => { +% #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)', +% desc =>'Shell ( set to blank for no shell tracking)', +% type =>'select', +% select_list => [ $conf->config('shells') ], +% disable_inventory => 1, +% }, +% 'finger' => 'Real name (GECOS)', +% 'domsvc' => { +% desc =>'svcnum from svc_domain', +% type =>'select', +% select_table => 'svc_domain', +% select_key => 'svcnum', +% select_label => 'domain', +% disable_inventory => 1, +% }, +% 'usergroup' => { +% desc =>'RADIUS groups', +% type =>'radius_usergroup_selector', +% disable_inventory => 1, +% }, +% 'seconds' => { desc => '', +% type => 'text', +% disable_inventory => 1, +% }, +% }, +% +% 'svc_domain' => { +% 'domain' => 'Domain', +% }, +% +% 'svc_forward' => { +% 'srcsvc' => 'service from which mail is to be forwarded', +% 'dstsvc' => 'service to which mail is to be forwarded', +% 'dst' => 'someone@another.domain.com to use when dstsvc is 0', +% }, +% +%# 'svc_charge' => { +%# 'amount' => 'amount', +%# }, +%# 'svc_wo' => { +%# 'worker' => 'Worker', +%# '_date' => 'Date', +%# }, +% +% 'svc_www' => { +% #'recnum' => '', +% #'usersvc' => '', +% }, +% +% 'svc_broadband' => { +% 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.', +% 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.', +% 'ip_addr' => 'IP address. Leave blank for automatic assignment.', +% 'blocknum' => 'Address block.', +% }, +% +% 'svc_phone' => { +% 'countrycode' => { desc => 'Country code', +% type => 'text', +% disable_inventory => 1, +% }, +% 'phonenum' => 'Phone number', +% 'pin' => { desc => 'Personal Identification Number', +% type => 'text', +% disable_inventory => 1, +% }, +% }, +% +% 'svc_external' => { +% #'id' => '', +% #'title' => '', +% }, +% +%); +% +% my %vfields; +% 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 +% my $pvf = $self->pvf($field); +% my @list = $pvf->list; +% if (scalar @list) { +% $defs{$svcdb}->{$field} = { desc => $pvf->label, +% type => 'select', +% select_list => \@list }; +% } else { +% $defs{$svcdb}->{$field} = $pvf->label; +% } #endif +% $vfields{$svcdb}->{$field} = $pvf; +% warn "\$vfields{$svcdb}->{$field} = $pvf"; +% } #next $field +% } #next $svcdb +% +% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm +% # and generalize the subs +% # condition sub is tested to see whether to disable display of this choice +% # params: ( $def, $layer, $field ) (see SUB below) +% my $inv_sub = sub { +% ref($_[0]) && ( $_[0]->{disable_inventory} +% || $_[0]->{'type'} ne 'text' ) +% }; +% tie my %flag, 'Tie::IxHash', +% '' => { 'desc' => 'No default', }, +% 'D' => { 'desc' => 'Default', +% 'condition' => +% sub { ref($_[0]) && $_[0]->{disable_default} }, +% }, +% 'F' => { 'desc' => 'Fixed (unchangeable)', +% 'condition' => +% sub { ref($_[0]) && $_[0]->{disable_fixed} }, +% }, +%# need to template-ize httemplate/edit/svc_* first +%# 'M' => { 'desc' => 'Manual selection from inventory', +%# 'condition' => $inv_sub, +%# }, +% 'A' => { 'desc' => 'Automatically fill in from inventory', +% 'condition' => $inv_sub, +% }, +% 'X' => { 'desc' => 'Excluded', +% 'condition' => +% sub { ! $vfields{$_[1]}->{$_[2]} }, +% +% }, +% ; +% +% my @dbs = $hashref->{svcdb} +% ? ( $hashref->{svcdb} ) +% : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_phone svc_external ); +% +% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; +% my $widget = new HTML::Widgets::SelectLayers( +% #'selected_layer' => $p_svcdb, +% 'selected_layer' => $hashref->{svcdb} || 'svc_acct', +% 'options' => \%svcdb, +% 'form_name' => 'dummy', +% #'form_action' => 'process/part_svc.cgi', +% 'form_action' => 'part_svc.cgi', #self +% 'form_text' => [ qw( svc svcpart ) ], +% 'form_checkbox' => [ 'disabled' ], +% 'layer_callback' => sub { +% my $layer = shift; +% +% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!; +% +% my $columns = 3; +% my $count = 0; +% my @part_export = +% map { qsearch( 'part_export', {exporttype => $_ } ) } +% keys %{FS::part_export::export_info($layer)}; +% $html .= '<BR><BR>'. table(). +% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>"; +% foreach my $part_export ( @part_export ) { +% $html .= '<TD><INPUT TYPE="checkbox"'. +% ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" '; +% $html .= 'CHECKED' +% if ( $clone || $part_svc->svcpart ) #null svcpart search causing error +% && qsearchs( 'export_svc', { +% exportnum => $part_export->exportnum, +% svcpart => $clone || $part_svc->svcpart }); +% $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype. +% ' to '. $part_export->machine. '</TD>'; +% $count++; +% $html .= '</TR><TR>' unless $count % $columns; +% } +% $html .= '</TR></TABLE><BR><BR>'; +% +% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ). +% '<TR>'. +% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'. +% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'. +% '</TR>'; +% +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% #yucky kludge +% my @fields = defined( dbdef->table($layer) ) +% ? grep { $_ ne 'svcnum' } fields($layer) +% : (); +% push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge +% $part_svc->svcpart($clone) if $clone; #haha, undone below +% +% +% foreach my $field (@fields) { +% +% my $part_svc_column = $part_svc->part_svc_column($field); +% my $value = $part_svc_column->columnvalue; +% my $flag = $part_svc_column->columnflag; +% my $def = $defs{$layer}{$field}; +% my $desc = ref($def) ? $def->{desc} : $def; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!. +% $field; +% $html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc; +% $html .= "</TD>"; +% $flag = '' if ref($def) && $def->{type} eq 'disabled'; +% +% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!; +% +% if ( ref($def) && $def->{type} eq 'disabled' ) { +% +% $html .= 'No default'; +% +% } else { +% +% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!. +% qq! onChange="${layer}__${field}_flag_changed(this)">!; +% +% foreach my $f ( keys %flag ) { +% +% #here is where the SUB from above is called, to skip some choices +% next if $flag{$f}->{condition} +% && &{ $flag{$f}->{condition} }( $def, $layer, $field ); +% +% $html .= qq!<OPTION VALUE="$f"!. +% ' SELECTED'x($flag eq $f ). +% '>'. $flag{$f}->{desc}; +% +% } +% +% $html .= '</SELECT>'; +% +% $html .= join("\n", +% '<SCRIPT>', +% " function ${layer}__${field}_flag_changed(what) {", +% ' var f = what.options[what.selectedIndex].value;', +% ' if ( f == "" || f == "X" ) { //disable', +% " what.form.${layer}__${field}.disabled = true;". +% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = true;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';". +% " }". +% ' } else if ( f == "D" || f == "F" ) { //enable, text box', +% " what.form.${layer}__${field}.disabled = false;". +% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}.style.display = '';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = false;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}_classnum.style.display = 'none';". +% " }". +% ' } else if ( f == "M" || f == "A" ) { //enable, inventory', +% " what.form.${layer}__${field}.disabled = false;". +% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}.style.display = 'none';". +% " if ( what.form.${layer}__${field}_classnum ) {". +% " what.form.${layer}__${field}_classnum.disabled = false;". +% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". +% " what.form.${layer}__${field}_classnum.style.display = '';". +% " }". +% ' }', +% ' }', +% '</SCRIPT>', +% ); +% +% } +% +% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!; +% +% my $disabled = $flag ? '' +% : 'DISABLED STYLE="background-color: #dddddd"'; +% +% if ( ! ref($def) || $def->{type} eq 'text' ) { +% +% my $nodisplay = ' STYLE="display:none"'; +% my $is_inv = ( $flag =~ /^[MA]$/ ); +% +% $html .= +% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !. +% $disabled. +% ( $is_inv ? $nodisplay : $disabled ). +% '>'; +% +% $html .= include('/elements/select-table.html', +% 'element_name' => "${layer}__${field}_classnum", +% 'element_etc' => ( $is_inv +% ? $disabled +% : $nodisplay +% ), +% 'table' => 'inventory_class', +% 'name_col' => 'classname', +% 'value' => $value, +% 'empty_label' => 'Select inventory class', +% ); +% +% } elsif ( $def->{type} eq 'select' ) { +% +% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled>!; +% $html .= '<OPTION> </OPTION>' unless $value; +% if ( $def->{select_table} ) { +% foreach my $record ( qsearch( $def->{select_table}, {} ) ) { +% my $rvalue = $record->getfield($def->{select_key}); +% $html .= qq!<OPTION VALUE="$rvalue"!. +% ( $rvalue==$value ? ' SELECTED>' : '>' ). +% $record->getfield($def->{select_label}). '</OPTION>'; +% } #next $record +% } else { # select_list +% foreach my $item ( @{$def->{select_list}} ) { +% $html .= qq!<OPTION VALUE="$item"!. +% ( $item eq $value ? ' SELECTED>' : '>' ). +% $item. '</OPTION>'; +% } #next $item +% } #endif +% $html .= '</SELECT>'; +% +% } elsif ( $def->{type} eq 'radius_usergroup_selector' ) { +% +% #XXX disable the RADIUS usergroup selector? ugh it sure does need +% #an overhaul, people have dum group problems because of it +% +% $html .= FS::svc_acct::radius_usergroup_selector( +% [ split(',', $value) ], "${layer}__${field}" ); +% +% } elsif ( $def->{type} eq 'disabled' ) { +% +% $html .= +% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!; +% +% } else { +% +% $html .= '<font color="#ff0000">unknown type'. $def->{type}; +% +% } +% +% $html .= "</TD></TR>\n"; +% +% } #foreach my $field (@fields) { +% +% $part_svc->svcpart('') if $clone; #undone +% $html .= "</TABLE>"; +% +% $html .= include('/elements/progress-init.html', +% $layer, #form name +% [ qw(svc svcpart disabled exportnum), @fields ], +% 'process/part_svc.cgi', +% $p.'browse/part_svc.cgi', +% $layer, +% ); +% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'. +% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '. +% ' onClick="document.'. "$layer.submit.disabled=true; ". +% "fixup(document.$layer); $layer". 'process();">'; +% +% #$html .= '<BR><INPUT TYPE="submit" VALUE="'. +% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">'; +% +% $html; +% +% }, +% ); +% +% + +Table <% $widget->html %> + </BODY> +</HTML> + diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi new file mode 100644 index 000000000..9dda4ebf9 --- /dev/null +++ b/httemplate/edit/part_virtual_field.cgi @@ -0,0 +1,103 @@ +% +%my ($vfieldpart, $part_virtual_field); +% +%if ( $cgi->param('error') ) { +% $part_virtual_field = new FS::part_virtual_field ( { +% map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')}); +% $vfieldpart = $part_virtual_field->vfieldpart; +%} else { +% my($query) = $cgi->keywords; +% if ( $query =~ /^(\d+)$/ ) { #editing +% $vfieldpart=$1; +% $part_virtual_field=qsearchs('part_virtual_field', +% {'vfieldpart' => $vfieldpart}) +% or die "Unknown vfieldpart!"; +% +% } else { #adding +% $part_virtual_field = new FS::part_virtual_field({}); +% } +%} +%my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +% +<% include('/elements/header.html', "$action Virtual Field Definition") %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + + +<FORM ACTION="<%$p1%>process/generic.cgi" METHOD="POST"> + +<INPUT TYPE="hidden" NAME="table" VALUE="part_virtual_field"> +<INPUT TYPE="hidden" NAME="redirect_ok" + VALUE="<%popurl(2)%>browse/part_virtual_field.cgi"> +<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<% + $vfieldpart%>"> +Field #<B><%$vfieldpart or "(NEW)"%></B><BR><BR> + +<%ntable("#cccccc",2)%> + <TR> + <TD ALIGN="right">Name</TD> + <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=15 VALUE="<% + $part_virtual_field->name%>"></TD> + </TR> + <TR> + <TD ALIGN="right">Table</TD> + <TD> +% if ($action eq 'Add') { + + <SELECT SIZE=1 NAME="dbtable"> +% +% my $dbdef = dbdef; # ick +% #foreach my $dbtable (sort { $a cmp $b } $dbdef->tables) { +% foreach my $dbtable (qw( svc_broadband )) { +% if ($dbtable !~ /^h_/ +% and $dbdef->table($dbtable)->primary_key) { + + <OPTION VALUE="<%$dbtable%>"><%$dbtable%></OPTION> +% +% } +% } +% +</SELECT> +% +% } else { # Edit +% +<%$part_virtual_field->dbtable%> + <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%$part_virtual_field->dbtable%>"> +% } + + </TD> + <TR> + <TD ALIGN="right">Label</TD> + <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="20" VALUE="<% + $part_virtual_field->label%>"></TD> + </TR> + <TR> + <TD ALIGN="right">Length</TD> + <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<% + $part_virtual_field->length%>"></TD> + </TR> + <TR> + <TD ALIGN="right">Check</TD> + <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><% + $part_virtual_field->check_block%></TEXTAREA></TD> + </TR> + <TR> + <TD ALIGN="right">List source</TD> + <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><% + $part_virtual_field->list_source%></TEXTAREA></TD> + </TR> +</TABLE><BR><INPUT TYPE="submit" VALUE="Submit"> + +</FORM> + +<BR> +<FONT SIZE=-2>If you don't understand what <I>check_block</I> and +<I>list_source</I> mean, <B>LEAVE THEM BLANK</B>. We mean it.</FONT> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html new file mode 100644 index 000000000..b79e4a976 --- /dev/null +++ b/httemplate/edit/payment_gateway.html @@ -0,0 +1,134 @@ +% +% +%my $payment_gateway; +%if ( $cgi->param('error') ) { +% $payment_gateway = new FS::payment_gateway ( { +% map { $_, scalar($cgi->param($_)) } fields('payment_gateway') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } ); +%} else { #adding +% $payment_gateway = new FS::payment_gateway {}; +%} +%my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add'; +%#my $hashref = $payment_gateway->hashref; +% +% + + +<% include("/elements/header.html","$action Payment gateway", menubar( + 'Main Menu' => $p, + 'View all payment gateways' => $p. 'browse/payment_gateway.html', +)) %> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>"> +Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %> + +<% ntable('#cccccc', 2, '') %> + +<TR> + <TH ALIGN="right">Gateway: </TH> + <TD> +% if ( $payment_gateway->gatewaynum ) { + + + <% $payment_gateway->gateway_module %> + <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>"> +% } else { + + + <SELECT NAME="gateway_module" SIZE=1> +% foreach my $module ( qw( +% 2CheckOut +% AuthorizeNet +% BankOfAmerica +% Beanstream +% Capstone +% Cardstream +% CashCow +% CyberSource +% eSec +% eSelectPlus +% Exact +% iAuthorizer +% IPaymentTPG +% Jettis +% LinkPoint +% MerchantCommerce +% Network1Financial +% OCV +% OpenECHO +% PayConnect +% PayflowPro +% PaymentsGateway +% PXPost +% SecureHostingUPG +% Skipjack +% StGeorge +% SurePay +% TCLink +% TransactionCentral +% VirtualNet +% ) ) { +% + + <OPTION VALUE="<% $module %>"><% $module %> +% } + + </SELECT> +% } + + + </TD> +</TR> + +<TR> + <TH ALIGN="right">Username: </TH> + <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD> +</TR> + +<TR> + <TH ALIGN="right">Password: </TH> + <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD> +</TR> + +<TR> + <TH ALIGN="right">Action: </TH> + <TD> + <SELECT NAME="gateway_action" SIZE=1> +% foreach my $action ( +% 'Normal Authorization', +% 'Authorization Only', +% 'Authorization Only, Post Authorization', +% ) { +% + + <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %> +% } + + </SELECT> + </TD> +</TR> + +<TR> + <TH ALIGN="right">Options: </TH> + <TD> + <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA> + </TD> +</TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>"> + </FORM> + </BODY> +</HTML> + diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html new file mode 100644 index 000000000..181072f71 --- /dev/null +++ b/httemplate/edit/pkg_class.html @@ -0,0 +1,10 @@ +<% include( 'elements/edit.html', + 'name' => 'Package Class', + 'table' => 'pkg_class', + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + }, + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi new file mode 100644 index 000000000..f563e253b --- /dev/null +++ b/httemplate/edit/prepay_credit.cgi @@ -0,0 +1,64 @@ +% +%my $agent = ''; +%my $agentnum = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agent = qsearchs('agent', { 'agentnum' => $agentnum=$1 } ); +%} +% +%tie my %multiplier, 'Tie::IxHash', +% 1 => 'seconds', +% 60 => 'minutes', +% 3600 => 'hours', +%; +% +%$cgi->param('multiplier', '60') unless $cgi->param('multiplier'); +% +% + + +<% include("/elements/header.html",'Generate prepaid cards'. ($agent ? ' for '. $agent->agent : ''), + menubar( 'Main Menu' => $p, )) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#FF0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/prepay_credit.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> + +Generate +<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') || '(quantity)' %>" SIZE=10 MAXLENGTH=10 onFocus="if ( this.value == '(quantity)' ) { this.value = ''; }"> +<SELECT NAME="type"> +% foreach (qw(alpha alphanumeric numeric)) { + + <OPTION<% $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><% $_ %> +% } + +</SELECT> + prepaid cards + +<BR>for <SELECT NAME="agentnum"><OPTION>(any agent) +% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) { + + <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %> +% } + +</SELECT> + +<BR>Value: +$<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<% $cgi->param('amount') %>"> +and/or +<INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('seconds') %>"> +<SELECT NAME="multiplier"> +% foreach my $multiplier ( keys %multiplier ) { + + <OPTION VALUE="<% $multiplier %>"<% $cgi->param('multiplier') eq $multiplier ? ' SELECTED' : '' %>><% $multiplier{$multiplier} %> +% } + +</SELECT> +<BR><BR> +<INPUT TYPE="submit" NAME="submit" VALUE="Generate" onSubmit="this.disabled = true"> + +</FORM></BODY></HTML> + diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi new file mode 100755 index 000000000..26e234fb0 --- /dev/null +++ b/httemplate/edit/process/REAL_cust_pkg.cgi @@ -0,0 +1,35 @@ +% +% +%my $pkgnum = $cgi->param('pkgnum') or die; +%my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%my %hash = $old->hash; +%$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : ''; +%$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : ''; +%$hash{'last_bill'} = +% $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : ''; +%$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : ''; +% +%my $new; +%my $error; +%if ( $hash{'bill'} != $old->bill # if the next bill date was changed +% && $hash{'bill'} < time # to a date in the past +% && ! $cgi->param('bill_areyousure') # and it wasn't confirmed +% ) +%{ +% $error = '_bill_areyousure'; +%} else { +% $new = new FS::cust_pkg \%hash; +% $error = $new->replace($old); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ); +%} else { +% my $custnum = $new->custnum; +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg$pkgnum" ); +%} +% +% + diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html new file mode 100644 index 000000000..c80311586 --- /dev/null +++ b/httemplate/edit/process/access_group.html @@ -0,0 +1,15 @@ +<% include( 'elements/process.html', + 'table' => 'access_group', + 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + }, + 'process_m2name' => { + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => 'FS::access_group', }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + }, + ) +%> diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html new file mode 100644 index 000000000..78857c525 --- /dev/null +++ b/httemplate/edit/process/access_user.html @@ -0,0 +1,8 @@ +<% include( 'elements/process.html', + 'table' => 'access_user', + 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + }, + ) +%> diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi new file mode 100755 index 000000000..85780c678 --- /dev/null +++ b/httemplate/edit/process/addr_block/add.cgi @@ -0,0 +1,21 @@ +% +% +%my $error = ''; +%my $ip_gateway = $cgi->param('ip_gateway'); +%my $ip_netmask = $cgi->param('ip_netmask'); +% +%my $new = new FS::addr_block { +% ip_gateway => $ip_gateway, +% ip_netmask => $ip_netmask, +% routernum => 0 }; +% +%$error = $new->insert; +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% + diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi new file mode 100755 index 000000000..a94c0320f --- /dev/null +++ b/httemplate/edit/process/addr_block/allocate.cgi @@ -0,0 +1,26 @@ +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +%my $routernum = $cgi->param('routernum'); +% +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +%my $router = qsearchs('router', { routernum => $routernum }); +% +%if($addr_block) { +% if ($router) { +% $error = $addr_block->allocate($router); +% } else { +% $error = "Cannot find router with routernum $routernum"; +% } +%} else { +% $error = "Cannot find block with blocknum $blocknum"; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% + diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi new file mode 100755 index 000000000..494c19f75 --- /dev/null +++ b/httemplate/edit/process/addr_block/deallocate.cgi @@ -0,0 +1,25 @@ +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +% +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +% +%if($addr_block) { +% my $router = $addr_block->router; +% if ($router) { +% $error = $addr_block->deallocate($router); +% } else { +% $error = "Block is not allocated to a router"; +% } +%} else { +% $error = "Cannot find block with blocknum $blocknum"; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% + diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi new file mode 100755 index 000000000..617c3f8ce --- /dev/null +++ b/httemplate/edit/process/addr_block/split.cgi @@ -0,0 +1,20 @@ +% +%my $error = ''; +%my $blocknum = $cgi->param('blocknum'); +%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum }); +% +%if ( $addr_block) { +% $error = $addr_block->split_block; +%} else { +% $error = "Unknown blocknum: $blocknum"; +%} +% +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(4). "browse/addr_block.cgi"); +%} +% + diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi new file mode 100755 index 000000000..5128d7ae8 --- /dev/null +++ b/httemplate/edit/process/agent.cgi @@ -0,0 +1,29 @@ +% +% +%my $agentnum = $cgi->param('agentnum'); +% +%my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum; +% +%my $new = new FS::agent ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('agent') +%} ); +% +%my $error; +%if ( $agentnum ) { +% $error=$new->replace($old); +%} else { +% $error=$new->insert; +% $agentnum=$new->getfield('agentnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/agent.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/agent_payment_gateway.html b/httemplate/edit/process/agent_payment_gateway.html new file mode 100644 index 000000000..436317ec4 --- /dev/null +++ b/httemplate/edit/process/agent_payment_gateway.html @@ -0,0 +1,26 @@ +% +% +%$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum"; +%my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +%die "agentnum $1 not found" unless $agent; +% +%#my $old +% +%my @new = map { +% my $cardtype = $_; +% new FS::agent_payment_gateway { +% ( map { $_ => scalar($cgi->param($_)) } +% fields('agent_payment_gateway') +% ), +% 'cardtype' => $cardtype, +% }; +% } +% $cgi->param('cardtype'); +% +%foreach my $new (@new) { +% my $error = $new->insert; +% die $error if $error; +%} +% +% +<% $cgi->redirect(popurl(3). "browse/agent.cgi") %> diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi new file mode 100755 index 000000000..b8d03705c --- /dev/null +++ b/httemplate/edit/process/agent_type.cgi @@ -0,0 +1,37 @@ +% +% +%my $typenum = $cgi->param('typenum'); +%my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum; +% +%my $new = new FS::agent_type ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('agent_type') +%} ); +% +%my $error; +%if ( $typenum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $typenum = $new->getfield('typenum'); +%} +%#$error ||= $new->process_m2m( ); +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ); +%} else { +% +% my $error = $new->process_m2m( +% 'link_table' => 'type_pkgs', +% 'target_table' => 'part_pkg', +% 'params' => scalar($cgi->Vars) +% ); +% die $error if $error; +% +% print $cgi->redirect(popurl(3). "browse/agent_type.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/bulk-cust_svc.cgi b/httemplate/edit/process/bulk-cust_svc.cgi new file mode 100644 index 000000000..ad4d67307 --- /dev/null +++ b/httemplate/edit/process/bulk-cust_svc.cgi @@ -0,0 +1,4 @@ +% +% my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process_bulk_cust_svc', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi new file mode 100755 index 000000000..962fc4eb9 --- /dev/null +++ b/httemplate/edit/process/cust_bill_pay.cgi @@ -0,0 +1,54 @@ +% +% +%$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ) +% or die "No such paynum"; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } ) +% or die "Bogus credit: not attached to customer"; +% +%my $custnum = $cust_main->custnum; +% +%my $new; +%if ($cgi->param('invnum') =~ /^Refund$/) { +% $new = new FS::cust_refund ( { +% 'reason' => 'Refunding payment', #enter reason in UI +% 'refund' => $cgi->param('amount'), +% 'payby' => 'BILL', +% #'_date' => $cgi->param('_date'), +% 'payinfo' => 'Cash', #enter payinfo in UI +% 'paynum' => $paynum, +% } ); +%} else { +% $new = new FS::cust_bill_pay ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(custnum _date amount invnum) +% } fields('cust_bill_pay') +% } ); +%} +% +%my $error = $new->insert; +% +%if ( $error ) { +% +% $cgi->param('error', $error); +% +<% $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %> +% +% +%} else { +% +% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +% +% +<% header('Payment application sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% } + diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi new file mode 100755 index 000000000..19faca47a --- /dev/null +++ b/httemplate/edit/process/cust_credit.cgi @@ -0,0 +1,38 @@ +% +% +%$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; +%my $custnum = $1; +% +%my $new = new FS::cust_credit ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('cust_credit') +%} ); +% +%my $error = $new->insert; +% +%if ( $error ) { +% $cgi->param('error', $error); +% +% +<% $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ) %> +% +% +%} else { +% +% if ( $cgi->param('apply') eq 'yes' ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum }) +% or die "unknown custnum $custnum"; +% $cust_main->apply_credits; +% } +% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +% +% +<% header('Credit sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% } + diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi new file mode 100755 index 000000000..7509a3f02 --- /dev/null +++ b/httemplate/edit/process/cust_credit_bill.cgi @@ -0,0 +1,55 @@ +% +% +%$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!"; +%my $crednum = $1; +% +%my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ) +% or die "No such crednum"; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } ) +% or die "Bogus credit: not attached to customer"; +% +%my $custnum = $cust_main->custnum; +% +%my $new; +%if ($cgi->param('invnum') =~ /^Refund$/) { +% $new = new FS::cust_refund ( { +% 'reason' => ( $cust_credit->reason || 'refund from credit' ), +% 'refund' => $cgi->param('amount'), +% 'payby' => 'BILL', +% #'_date' => $cgi->param('_date'), +% #'payinfo' => 'Cash', +% 'payinfo' => 'Refund', +% 'crednum' => $crednum, +% } ); +%} else { +% $new = new FS::cust_credit_bill ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(custnum _date amount invnum) +% } fields('cust_credit_bill') +% } ); +%} +% +%my $error = $new->insert; +% +%if ( $error ) { +% +% $cgi->param('error', $error); +% +<% $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %> +% +% +%} else { +% +% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +% +% +<% header('Credit application sucessful') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% } + diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi new file mode 100755 index 000000000..33f7bb895 --- /dev/null +++ b/httemplate/edit/process/cust_main.cgi @@ -0,0 +1,159 @@ +% +% +%my $error = ''; +% +%#unmunge stuff +% +%$cgi->param('tax','') unless defined $cgi->param('tax'); +% +%$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] ); +% +%#my $payby = $cgi->param('payby'); +%my $payby = $cgi->param('select'); # XXX key +% +%my %noauto = ( +% 'CARD' => 'DCRD', +% 'CHEK' => 'DCHK', +%); +%$payby = $noauto{$payby} +% if ! $cgi->param('payauto') && exists $noauto{$payby}; +% +%$cgi->param('payby', $payby); +% +%if ( $payby ) { +% if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) { +% $cgi->param('payinfo', +% $cgi->param('payinfo1'). '@'. $cgi->param('payinfo2') ); +% } +% $cgi->param('paydate', +% $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) ); +%} +% +%my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); +%push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); +%push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX'); +%$cgi->param('invoicing_list', join(',', @invoicing_list) ); +% +% +%#create new record object +% +%my $new = new FS::cust_main ( { +% map { +% $_, scalar($cgi->param($_)) +%# } qw(custnum agentnum last first ss company address1 address2 city county +%# state zip daytime night fax payby payinfo paydate payname tax +%# otaker refnum) +% } fields('cust_main') +%} ); +% +%if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) { +% $new->setfield("ship_$_", '') foreach qw( +% last first company address1 address2 city county state zip +% country daytime night fax +% ); +%} +% +%if ( $cgi->param('birthdate') && $cgi->param('birthdate') =~ /^([ 0-9\-\/]{0,10})$/ ) { +% $new->setfield('birthdate', str2time($1)); +%} +% +%$new->setfield('paid', $cgi->param('paid') ) +% if $cgi->param('paid'); +% +%#perhaps this stuff should go to cust_main.pm +%my $cust_pkg = ''; +%my $svc_acct = ''; +%if ( $new->custnum eq '' ) { +% +% if ( $cgi->param('pkgpart_svcpart') ) { +% my $x = $cgi->param('pkgpart_svcpart'); +% $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n"; +% my($pkgpart, $svcpart) = ($1, $2); +% #false laziness: copied from FS::cust_pkg::order (which should become a +% #FS::cust_main method) +% my(%part_pkg); +% # generate %part_pkg +% # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart +% my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum }); +% #my($type_pkgs); +% #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { +% # my($pkgpart)=$type_pkgs->pkgpart; +% # $part_pkg{$pkgpart}++; +% #} +% # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart +% my $pkgpart_href = $agent->pkgpart_hashref; +% #eslaf +% +% # this should wind up in FS::cust_pkg! +% $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ". +% "purchase pkgpart ". $pkgpart +% #unless $part_pkg{ $pkgpart }; +% unless $pkgpart_href->{ $pkgpart }; +% +% $cust_pkg = new FS::cust_pkg ( { +% #later 'custnum' => $custnum, +% 'pkgpart' => $pkgpart, +% } ); +% #$error ||= $cust_pkg->check; +% +% #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } ); +% +% #$error ||= $cust_svc->check; +% +% $svc_acct = new FS::svc_acct ( { +% 'svcpart' => $svcpart, +% 'username' => $cgi->param('username'), +% '_password' => $cgi->param('_password'), +% 'popnum' => $cgi->param('popnum'), +% } ); +% +% #and just in case you were silly +% $svc_acct->svcpart($svcpart); +% $svc_acct->username($cgi->param('username')); +% $svc_acct->_password($cgi->param('_password')); +% $svc_acct->popnum($cgi->param('popnum')); +% +% #$error ||= $svc_acct->check; +% +% } elsif ( $cgi->param('username') ) { #good thing to catch +% $error = "Can't assign username without a package!"; +% } +% +% use Tie::RefHash; +% tie my %hash, 'Tie::RefHash'; +% %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg; +% $error ||= $new->insert( \%hash, \@invoicing_list ); +% +% my $conf = new FS::Conf; +% if ( $conf->exists('backend-realtime') && ! $error ) { +% +% my $berror = $new->bill; +% $new->apply_payments; +% $new->apply_credits; +% $berror ||= $new->collect( 'realtime' => 1 ); +% warn "Warning, error billing during backend-realtime: $berror" if $berror; +% +% } +% +%} 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); +% +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum); +%} +% + + diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi new file mode 100755 index 000000000..4bcaf1de3 --- /dev/null +++ b/httemplate/edit/process/cust_main_county-collapse.cgi @@ -0,0 +1,36 @@ +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ or die "Illegal taxnum!"; +%my $taxnum = $1; +%my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } ) +% or die "Unknown taxnum $taxnum"; +% +%#really should do this in a .pm & start transaction +% +%foreach my $delete ( qsearch('cust_main_county', { +% 'country' => $cust_main_county->country, +% 'state' => $cust_main_county->state +% } ) ) { +%# unless ( qsearch('cust_main',{ +%# 'state' => $cust_main_county->getfield('state'), +%# 'county' => $cust_main_county->getfield('county'), +%# 'country' => $cust_main_county->getfield('country'), +%# } ) ) { +% my $error = $delete->delete; +% die $error if $error; +%# } else { +% #should really fix the $cust_main record +%# } +% +%} +% +%$cust_main_county->taxnum(''); +%$cust_main_county->county(''); +%my $error = $cust_main_county->insert; +%die $error if $error; +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% + diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi new file mode 100755 index 000000000..e550e8b4a --- /dev/null +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -0,0 +1,59 @@ +% +% +%$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!"; +%my $taxnum = $1; +%my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +% or die ("Unknown taxnum!"); +% +%my @expansion; +%if ( $cgi->param('delim') eq 'n' ) { +% @expansion=split(/\n/,$cgi->param('expansion')); +%} elsif ( $cgi->param('delim') eq 's' ) { +% @expansion=split(' ',$cgi->param('expansion')); +%} else { +% die "Illegal delim!"; +%} +% +%@expansion=map { +% unless ( /^\s*([\w\- ]+)\s*$/ ) { +% $cgi->param('error', "Illegal item in expansion"); +% print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string ); +% myexit(); +% } +% $1; +%} @expansion; +% +%foreach ( @expansion) { +% my(%hash)=$cust_main_county->hash; +% my($new)=new FS::cust_main_county \%hash; +% $new->setfield('taxnum',''); +% if ( $cgi->param('taxclass') ) { +% $new->setfield('taxclass', $_); +% } elsif ( ! $cust_main_county->state ) { +% $new->setfield('state',$_); +% } else { +% $new->setfield('county',$_); +% } +% #if (datasrc =~ m/Pg/) +% #{ +% # $new->setfield('tax',0.0); +% #} +% my($error)=$new->insert; +% die $error if $error; +%} +% +%unless ( qsearch( 'cust_main', { +% 'state' => $cust_main_county->state, +% 'county' => $cust_main_county->county, +% 'country' => $cust_main_county->country, +% } ) +% || ! @expansion +%) { +% my($error)=($cust_main_county->delete); +% die $error if $error; +%} +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% + diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi new file mode 100755 index 000000000..2c3ebe866 --- /dev/null +++ b/httemplate/edit/process/cust_main_county.cgi @@ -0,0 +1,31 @@ +% +% +%foreach ( grep { /^tax\d+$/ } $cgi->param ) { +% /^tax(\d+)$/ or die "Illegal form $_!"; +% my $taxnum = $1; +% my $old = qsearchs('cust_main_county', { 'taxnum' => $taxnum }) +% 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->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 ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_main_county.cgi?". $cgi->query_string ); +% myexit(); +% } +%} +% +%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); +% +% + diff --git a/httemplate/edit/process/cust_main_note.cgi b/httemplate/edit/process/cust_main_note.cgi new file mode 100755 index 000000000..d9251f042 --- /dev/null +++ b/httemplate/edit/process/cust_main_note.cgi @@ -0,0 +1,34 @@ +% +% +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die "Illegal custnum: ". $cgi->param('custnum'); +%my $custnum = $1; +% +%my $otaker = $FS::CurrentUser::CurrentUser->name; +%$otaker = $FS::CurrentUser::CurrentUser->username +% if ($otaker eq "User, Legacy"); +% +%my $new = new FS::cust_main_note ( { +% custnum => $custnum, +% _date => time, +% otaker => $otaker, +% comments => $cgi->param('comment'), +%} ); +% +%my $error = $new->insert; +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). 'cust_main_note.cgi?'. $cgi->query_string ); +%} +% +% +<% header('Note added') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% +% + diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi new file mode 100755 index 000000000..68342ee04 --- /dev/null +++ b/httemplate/edit/process/cust_pay.cgi @@ -0,0 +1,56 @@ +% +% +%$cgi->param('linknum') =~ /^(\d+)$/ +% or die "Illegal linknum: ". $cgi->param('linknum'); +%my $linknum = $1; +% +%$cgi->param('link') =~ /^(custnum|invnum|popup)$/ +% or die "Illegal link: ". $cgi->param('link'); +%my $field = my $link = $1; +%$field = 'custnum' if $field eq 'popup'; +% +%my $_date = str2time($cgi->param('_date')); +% +%my $new = new FS::cust_pay ( { +% $field => $linknum, +% _date => $_date, +% map { +% $_, scalar($cgi->param($_)); +% } qw(paid payby payinfo paybatch) +% #} fields('cust_pay') +%} ); +% +%my $error = $new->insert; +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ); +%} elsif ( $field eq 'invnum' ) { +% print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum"); +%} elsif ( $field eq 'custnum' ) { +% if ( $cgi->param('apply') eq 'yes' ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum }) +% or die "unknown custnum $linknum"; +% $cust_main->apply_payments; +% } +% if ( $link eq 'popup' ) { +% +% +<% header('Payment entered') %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + + </BODY></HTML> +% +% +% } elsif ( $link eq 'custnum' ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum"); +% } else { +% die "unknown link $link"; +% } +% +%} +% +% + diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi new file mode 100755 index 000000000..817c88087 --- /dev/null +++ b/httemplate/edit/process/cust_pkg.cgi @@ -0,0 +1,44 @@ +% +% +%my $error = ''; +% +%#untaint custnum +%$cgi->param('custnum') =~ /^(\d+)$/; +%my $custnum = $1; +% +%my @remove_pkgnums = map { +% /^(\d+)$/ or die "Illegal remove_pkg value!"; +% $1; +%} $cgi->param('remove_pkg'); +% +%my $error_redirect; +%my @pkgparts; +%if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi +% $error_redirect = "misc/change_pkg.cgi"; +% @pkgparts = ($1); +%} else { #came from edit/cust_pkg.cgi +% $error_redirect = "edit/cust_pkg.cgi"; +% foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) { +% if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) { +% my $num_pkgs = $1; +% while ( $num_pkgs-- ) { +% push @pkgparts,$pkgpart; +% } +% } else { +% $error = "Illegal quantity"; +% last; +% } +% } +%} +% +%$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums); +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} +% +% + diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi new file mode 100755 index 000000000..a579a02d8 --- /dev/null +++ b/httemplate/edit/process/cust_refund.cgi @@ -0,0 +1,34 @@ +%$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; +%my $custnum = $1; +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) +% or die "unknown custnum $custnum"; +% +%my $error = ''; +%if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { +% my $bop = $FS::payby::payby2bop{$1}; +% $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/ +% or die "illegal refund amount ". $cgi->param('refund'); +% my $refund = "$1$2"; +% $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; +% my $paynum = $1; +% my $reason = $cgi->param('reason'); +% $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund, +% 'paynum' => $paynum, +% 'reason' => $reason, ); +%} else { +% die 'unimplemented'; +% #my $new = new FS::cust_refund ( { +% # map { +% # $_, scalar($cgi->param($_)); +% # } ( fields('cust_refund'), 'paynum' ) +% #} ); +% #$error = $new->insert; +%} +% +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi new file mode 100644 index 000000000..3a07d1e7a --- /dev/null +++ b/httemplate/edit/process/cust_svc.cgi @@ -0,0 +1,30 @@ +% +% +%my $svcnum = $cgi->param('svcnum'); +% +%my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::cust_svc ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('cust_svc') +%} ); +% +%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). "cust_svc.cgi?". $cgi->query_string ); +% eidiot($error); +%} else { +% my $svcdb = $new->part_svc->svcdb; +% print $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum"); +%} +% +% diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi new file mode 100755 index 000000000..87bdf6835 --- /dev/null +++ b/httemplate/edit/process/domain_record.cgi @@ -0,0 +1,36 @@ +% +% +%my $recnum = $cgi->param('recnum'); +% +%my $old = qsearchs('agent',{'recnum'=>$recnum}) if $recnum; +% +%my $new = new FS::domain_record ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('domain_record') +%} ); +% +%my $error; +%if ( $recnum ) { +% $error=$new->replace($old); +%} else { +% $error=$new->insert; +% $recnum=$new->getfield('recnum'); +%} +% +%if ( $error ) { +%# $cgi->param('error', $error); +%# print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); +% #no edit screen to send them back to +% + +<!-- mason kludge --> +% +% eidiot($error); +%} else { +% my $svcnum = $new->svcnum; +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% + diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html new file mode 100644 index 000000000..96d568754 --- /dev/null +++ b/httemplate/edit/process/elements/process.html @@ -0,0 +1,92 @@ +% +% +% # options example... +% # +% ### +% ##req +% ## +% # +% # 'table' => +% # +% # #? 'primary_key' => #required when the dbdef doesn't know...??? +% # #? 'fields' => [] +% # +% ### +% ##opt +% ### +% # +% # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' +% # OR +% # 'redirect' => 'view/table.cgi?', # value of primary key is appended +% # +% # 'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the +% # #naming is still inconsistent +% # +% # 'process_m2m' => { 'link_table' => 'link_table_name', +% # 'target_table' => 'target_table_name', +% # }, +% # 'process_m2name' => { 'link_table' => 'link_table_name', +% # 'link_static' => { 'column' => 'value' }, +% # 'num_col' => 'column', #if column name is different in +% # #link_table than source_table +% # 'name_col' => 'name_column', +% # 'names_list' => [ 'list', 'names' ], +% # }, +% +% my(%opt) = @_; +% +% #false laziness w/edit.html +% my $table = $opt{'table'}; +% my $class = "FS::$table"; +% my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || +% my $fields = $opt{'fields'} +% #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; +% || [ fields($table) ]; +% +% my $pkeyvalue = $cgi->param($pkey); +% +% my $old = qsearchs( $table, { $pkey => $pkeyvalue } ) if $pkeyvalue; +% +% my $new = $class->new( { +% map { +% $_, scalar($cgi->param($_)); +% } @$fields +% } ); +% +% my $error; +% if ( $pkeyvalue ) { +% $error = $new->replace($old); +% } else { +% $error = $new->insert; +% $pkeyvalue = $new->getfield($pkey); +% } +% +% if ( !$error && $opt{'process_m2m'} ) { +% $error = $new->process_m2m( %{ $opt{'process_m2m'} }, +% 'params' => scalar($cgi->Vars), +% ); +% } +% +% if ( !$error && $opt{'process_m2name'} ) { +% $error = $new->process_m2name( %{ $opt{'process_m2name'} }, +% 'params' => scalar($cgi->Vars), +% ); +% } +% +% # XXX print?!?! +% +% if ( $error ) { +% $cgi->param('error', $error); +% my $edit_ext = $opt{'edit_ext'} || 'html'; +% print $cgi->redirect(popurl(2). "$table.$edit_ext?". $cgi->query_string ); +% } elsif ( $opt{'redirect'} ) { +% print $cgi->redirect( $opt{'redirect'}. $pkeyvalue ); +% } else { +% print $cgi->redirect( popurl(3). +% ( $opt{'viewall_dir'} || 'search' ). +% "/$table.html" +% ); +% } +% +% + diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html new file mode 100644 index 000000000..8e8c99a42 --- /dev/null +++ b/httemplate/edit/process/elements/svc_Common.html @@ -0,0 +1,15 @@ +% +% +% my %opt = @_; +% my $table = $opt{'table'}; +% $opt{'fields'} ||= [ fields($table) ]; +% push @{ $opt{'fields'} }, qw( pkgnum svcpart ); +% +% +<% include( 'process.html', + 'edit_ext' => 'cgi', + 'redirect' => popurl(3)."view/$table.cgi?", + %opt, + ) +%> + diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi new file mode 100644 index 000000000..e3ac113ae --- /dev/null +++ b/httemplate/edit/process/generic.cgi @@ -0,0 +1,73 @@ +%# Welcome to generic.cgi. +%# +%# This script provides a generic edit/process/ backend for simple table +%# editing. All it knows how to do is take the values entered into +%# the script and insert them into the table specified by $cgi->param('table'). +%# If there's an existing record with the same primary key, it will be +%# replaced. (Deletion will be added in the future.) +%# +%# also see elements/process.html, newer and somewhat along the same lines, +%# though it still makes you setup a process file for the table. +%# perhaps safer, perhaps more of a pain in the ass. +%# +%# Special cgi params for this script: +%# table: the name of the table to be edited. The script will die horribly +%# if it can't find the table. +%# redirect_ok: URL to be displayed after a successful edit. The value of +%# the record's primary key will be passed as a keyword. +%# Defaults to (freeside root)/view/$table.cgi. +%# redirect_error: URL to be displayed if there's an error. The original +%# query string, plus the error message, will be passed. +%# Defaults to $cgi->referer() (i.e. go back where you +%# came from). +% +% +%use FS::Record qw(qsearchs dbdef); +%use DBIx::DBSchema; +%use DBIx::DBSchema::Table; +% +% +%my $error; +%my $p2 = popurl(2); +%my $p3 = popurl(3); +%my $table = $cgi->param('table'); +%my $dbdef = dbdef or die "Cannot fetch dbdef!"; +% +%my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table"; +% +%my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table"; +%my $pkey_val = $cgi->param($pkey); +% +% +%#warn "new FS::Record ( $table, (hashref) )"; +%my $new = FS::Record::new ( "FS::$table", { +% map { $_, scalar($cgi->param($_)) } fields($table) +%} ); +% +%#warn 'created $new of class '.ref($new); +% +%if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) { +% # edit +% $error = $new->replace($old); +%} else { +% #add +% $error = $new->insert; +% $pkey_val = $new->getfield($pkey); +% # New records usually don't have their primary keys set until after +% # they've been checked/inserted, so grab the new $pkey_val so we can +% # redirect to it. +%} +% +%my $redirect_ok = (($cgi->param('redirect_ok')) ? +% $cgi->param('redirect_ok') : $p3."browse/generic.cgi?$table"); +%my $redirect_error = (($cgi->param('redirect_error')) ? +% $cgi->param('redirect_error') : $cgi->referer()); +% +%if($error) { +% $cgi->param('error', $error); +% print $cgi->redirect($redirect_error . '?' . $cgi->query_string); +%} else { +% print $cgi->redirect($redirect_ok); +%} +% + diff --git a/httemplate/edit/process/inventory_class.html b/httemplate/edit/process/inventory_class.html new file mode 100644 index 000000000..c7be9e8dd --- /dev/null +++ b/httemplate/edit/process/inventory_class.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'inventory_class', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/msgcat.cgi b/httemplate/edit/process/msgcat.cgi new file mode 100644 index 000000000..9711143d6 --- /dev/null +++ b/httemplate/edit/process/msgcat.cgi @@ -0,0 +1,21 @@ +% +% +%my $error; +%foreach my $param ( grep { /^\d+$/ } $cgi->param ) { +% my $old = qsearchs('msgcat', { msgnum=>$param } ); +% next if $old->msg eq $cgi->param($param); #no need to update identical records +% my $new = new FS::msgcat { $old->hash }; +% $new->msg($cgi->param($param)); +% $error = $new->replace($old); +% last if $error; +%} +% +%if ( $error ) { +% $cgi->param('error',$error); +% print $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/msgcat.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi new file mode 100755 index 000000000..4811d9c9b --- /dev/null +++ b/httemplate/edit/process/part_bill_event.cgi @@ -0,0 +1,55 @@ +% +% +%my $eventpart = $cgi->param('eventpart'); +% +%my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart; +% +%#s/days/seconds/ +%$cgi->param('seconds', int( $cgi->param('days') * 86400 ) ); +% +%my $error; +%if ( ! $cgi->param('plan_weight_eventcode') ) { +% $error = "Must select an action"; +%} else { +% +% $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s +% or die "illegal plan_weight_eventcode:". +% $cgi->param('plan_weight_eventcode'); +% $cgi->param('plan', $1); +% $cgi->param('weight', $2); +% my $eventcode = $3; +% my $plandata = ''; +% while ( $eventcode =~ /%%%(\w+)%%%/ ) { +% my $field = $1; +% my $value = join(', ', $cgi->param($field) ); +% $cgi->param($field, $value); #in case it errors out +% $eventcode =~ s/%%%$field%%%/$value/; +% $plandata .= "$field $value\n"; +% } +% $cgi->param('eventcode', $eventcode); +% $cgi->param('plandata', $plandata); +% +% my $new = new FS::part_bill_event ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('part_bill_event'), +% } ); +% +% if ( $eventpart ) { +% $error = $new->replace($old); +% } else { +% $error = $new->insert; +% $eventpart = $new->getfield('eventpart'); +% } +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3)."browse/part_bill_event.cgi"); +%} +% +% + + diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi new file mode 100644 index 000000000..0dd9eabae --- /dev/null +++ b/httemplate/edit/process/part_export.cgi @@ -0,0 +1,40 @@ +% +% +%my $exportnum = $cgi->param('exportnum'); +% +%my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum; +% +%#fixup options +%#warn join('-', split(',',$cgi->param('options'))); +%my %options = map { +% my $value = $cgi->param($_); +% $value =~ s/\r\n/\n/g; #browsers? (textarea) +% $_ => $value; +%} split(',', $cgi->param('options')); +% +%my $new = new FS::part_export ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('part_export') +%} ); +% +%my $error; +%if ( $exportnum ) { +% #warn $old; +% #warn $exportnum; +% #warn $new->machine; +% $error = $new->replace($old,\%options); +%} else { +% $error = $new->insert(\%options); +%# $exportnum = $new->exportnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error ); +% print $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/part_export.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi new file mode 100755 index 000000000..204c751d9 --- /dev/null +++ b/httemplate/edit/process/part_pkg.cgi @@ -0,0 +1,62 @@ +% +% +%my $dbh = dbh; +% +%my $pkgpart = $cgi->param('pkgpart'); +% +%my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart; +% +%#fixup plandata +%my $plandata = $cgi->param('plandata'); +%my @plandata = split(',', $plandata); +%$cgi->param('plandata', +% join('', map { "$_=". join(', ', $cgi->param($_)). "\n" } @plandata ) +%); +% +%foreach (qw( setuptax recurtax disabled )) { +% $cgi->param($_, '') unless defined $cgi->param($_); +%} +% +%my $new = new FS::part_pkg ( { +% map { +% $_ => scalar($cgi->param($_)); +% } fields('part_pkg') +%} ); +% +%my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) } +% map { $_->svcpart } +% qsearch('part_svc', {} ); +% +%my $error; +%my $custnum = ''; +%if ( $cgi->param('taxclass') eq '(select)' ) { +% +% $error = 'Must select a tax class'; +% +%} elsif ( $pkgpart ) { +% +% $error = $new->replace( $old, +% pkg_svc => \%pkg_svc, +% primary_svc => scalar($cgi->param('pkg_svc_primary')), +% ); +%} else { +% +% $error = $new->insert( pkg_svc => \%pkg_svc, +% primary_svc => scalar($cgi->param('pkg_svc_primary')), +% cust_pkg => $cgi->param('pkgnum'), +% custnum_ref => \$custnum, +% ); +% $pkgpart = $new->pkgpart; +%} +% +%if ( $error ) { +% $cgi->param('error', $error ); +% print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ); +%} elsif ( $custnum ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +%} else { +% print $cgi->redirect(popurl(3). "browse/part_pkg.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html new file mode 100755 index 000000000..14c1b7001 --- /dev/null +++ b/httemplate/edit/process/part_referral.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'part_referral', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi new file mode 100755 index 000000000..97abc5baf --- /dev/null +++ b/httemplate/edit/process/part_svc.cgi @@ -0,0 +1,4 @@ +% +% my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html new file mode 100644 index 000000000..0b7e31395 --- /dev/null +++ b/httemplate/edit/process/payment_gateway.html @@ -0,0 +1,34 @@ +% +% +%my $gatewaynum = $cgi->param('gatewaynum'); +% +%my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum; +% +%my $new = new FS::payment_gateway ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('payment_gateway') +%} ); +% +%my @options = split(/\r?\n/, $cgi->param('gateway_options') ); +%pop @options +% if scalar(@options) % 2 && $options[-1] =~ /^\s*$/; +%my %options = @options; +% +%my $error; +%if ( $gatewaynum ) { +% $error=$new->replace($old, \%options); +%} else { +% $error=$new->insert(\%options); +% $gatewaynum=$new->getfield('gatewaynum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/payment_gateway.html"); +%} +% +% + diff --git a/httemplate/edit/process/pkg_class.html b/httemplate/edit/process/pkg_class.html new file mode 100644 index 000000000..183da805c --- /dev/null +++ b/httemplate/edit/process/pkg_class.html @@ -0,0 +1,5 @@ +<% include( 'elements/process.html', + 'table' => 'pkg_class', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/prepay_credit.cgi b/httemplate/edit/process/prepay_credit.cgi new file mode 100644 index 000000000..fb15fd8e4 --- /dev/null +++ b/httemplate/edit/process/prepay_credit.cgi @@ -0,0 +1,57 @@ +% +%my $hashref = {}; +% +%my $agent = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agent = qsearchs('agent', { 'agentnum' => $hashref->{agentnum}=$1 } ); +%} +% +%my $error = ''; +% +%my $num = 0; +%if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { +% $num = $1; +%} else { +% $error = 'Illegal number of prepaid cards: '. $cgi->param('num'); +%} +% +%$hashref->{amount} = $cgi->param('amount'); +%$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier'); +% +%$error ||= FS::prepay_credit::generate( $num, +% scalar($cgi->param('type')), +% $hashref +% ); +% +%unless ( ref($error) ) { +% $cgi->param('error', $error ); +% +<% + $cgi->redirect(popurl(3). "edit/prepay_credit.cgi?". $cgi->query_string ) +%> +% } else { + + +<% include("/elements/header.html", "$num prepaid cards generated". + ( $agent ? ' for '.$agent->agent : '' ), + menubar( 'Main menu' => popurl(3) ) + ) +%> + +<FONT SIZE="+1"> +% foreach my $card ( @$error ) { + + <code><% $card %></code> + - + <% $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %> + <% $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %> + <% $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %> + <br> +% } + + +</FONT> + +</BODY></HTML> +% } + diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi new file mode 100644 index 000000000..70778c1cc --- /dev/null +++ b/httemplate/edit/process/quick-charge.cgi @@ -0,0 +1,43 @@ +% +% +%#untaint custnum +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die 'illegal custnum '. $cgi->param('custnum'); +%my $custnum = $1; +% +%$cgi->param('amount') =~ /^\s*(\d+(\.\d{1,2})?)\s*$/ +% or die 'illegal amount '. $cgi->param('amount'); +%my $amount = $1; +% +%my( $error, $cust_main); +%if ( $cgi->param('taxclass') eq '(select)' ) { +% +% +% $error = 'Must select a tax class'; +%} else { +% +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) +% or die "unknown custnum $custnum"; +% +% $error = $cust_main->charge( +% $amount, +% $cgi->param('pkg'), +% '$'. sprintf("%.2f",$amount), +% $cgi->param('taxclass') +% ); +% +%} +% +%if ($error) { +% + +<!-- mason kludge --> +% +% eidiot($error); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum" ); +%} +% +% + + diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi new file mode 100644 index 000000000..7afc9f2bb --- /dev/null +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -0,0 +1,27 @@ +% +% +%#untaint custnum +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die 'illegal custnum '. $cgi->param('custnum'); +%my $custnum = $1; +%$cgi->param('pkgpart') =~ /^(\d+)$/ +% or die 'illegal pkgpart '. $cgi->param('pkgpart'); +%my $pkgpart = $1; +% +%my @cust_pkg = (); +%my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, ); +% +%if ($error) { +% + +<!-- mason kludge --> +% +% eidiot($error); +%} else { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg". $cust_pkg[0]->pkgnum ); +%} +% +% + + diff --git a/httemplate/edit/process/rate.cgi b/httemplate/edit/process/rate.cgi new file mode 100755 index 000000000..c81f883b7 --- /dev/null +++ b/httemplate/edit/process/rate.cgi @@ -0,0 +1,4 @@ +% +% my $server = new FS::UI::Web::JSRPC 'FS::rate::process', $cgi; +% +<% $server->process %> diff --git a/httemplate/edit/process/rate_region.cgi b/httemplate/edit/process/rate_region.cgi new file mode 100755 index 000000000..753224565 --- /dev/null +++ b/httemplate/edit/process/rate_region.cgi @@ -0,0 +1,52 @@ +% +% +%my $regionnum = $cgi->param('regionnum'); +% +%my $old = qsearchs('rate_region', { 'regionnum' => $regionnum } ) if $regionnum; +% +%my $new = new FS::rate_region ( { +% map { +% $_, scalar($cgi->param($_)); +% } ( fields('rate_region') ) +%} ); +% +%my $countrycode = $cgi->param('countrycode'); +%my @npa = split(/\s*,\s*/, $cgi->param('npa')); +%$npa[0] = '' unless @npa; +%my @rate_prefix = map { +% new FS::rate_prefix { +% 'countrycode' => $countrycode, +% 'npa' => $_, +% } +% } @npa; +% +%my @dest_detail = map { +% my $ratenum = $_->ratenum; +% new FS::rate_detail { +% 'ratenum' => $ratenum, +% map { $_ => $cgi->param("$_$ratenum") } +% qw( min_included min_charge sec_granularity ) +% }; +%} qsearch('rate', {} ); +% +% +%my $error; +%if ( $regionnum ) { +% $error = $new->replace($old, 'rate_prefix' => \@rate_prefix, +% 'dest_detail' => \@dest_detail, ); +%} else { +% $error = $new->insert( 'rate_prefix' => \@rate_prefix, +% 'dest_detail' => \@dest_detail, ); +% $regionnum = $new->getfield('regionnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "rate_region.cgi?". $cgi->query_string ); +%} else { +% #print $cgi->redirect(popurl(3). "browse/rate_region.cgi"); +% print $cgi->redirect(popurl(3). "browse/rate.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/reg_code.cgi b/httemplate/edit/process/reg_code.cgi new file mode 100644 index 000000000..4fdea60fc --- /dev/null +++ b/httemplate/edit/process/reg_code.cgi @@ -0,0 +1,50 @@ +% +% +%$cgi->param('agentnum') =~ /^(\d+)$/ +% or eidiot 'illegal agentnum '. $cgi->param('agentnum'); +%my $agentnum = $1; +%my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +%my $error = ''; +% +%my $num = 0; +%if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) { +% $num = $1; +%} else { +% $error = 'Illegal number of codes: '. $cgi->param('num'); +%} +% +%my @pkgparts = +% map { /^pkgpart(.*)$/; $1 } +% grep { $cgi->param($_) } +% grep { /^pkgpart/ } +% $cgi->param; +% +%$error ||= $agent->generate_reg_codes($num, \@pkgparts); +% +%unless ( ref($error) ) { +% $cgi->param('error'. $error ); +% +<% + $cgi->redirect(popurl(3). "edit/reg_code.cgi?". $cgi->query_string ) +%> +% } else { + + +<% include("/elements/header.html","$num registration codes generated for ". $agent->agent, menubar( + 'Main menu' => popurl(3), + 'View all agents' => popurl(3). 'browse/agent.cgi', +) ) %> + +<PRE><FONT SIZE="+1"> +% foreach my $code ( @$error ) { + + <% $code %> +% } + + +</FONT></PRE> + +</BODY></HTML> +% } + diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi new file mode 100644 index 000000000..c69114ea4 --- /dev/null +++ b/httemplate/edit/process/router.cgi @@ -0,0 +1,68 @@ +% +% +%local $FS::UID::AutoCommit=0; +% +%sub check { +% my $error = shift; +% if($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string); +% dbh->rollback; +% exit; +% } +%} +% +%my $error = ''; +%my $routernum = $cgi->param('routernum'); +%my $routername = $cgi->param('routername'); +%my $old = qsearchs('router', { routernum => $routernum }); +%my @old_psr; +% +%my $new = new FS::router { +% map { +% ($_, scalar($cgi->param($_))); +% } fields('router') +%}; +% +%if($old) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $routernum = $new->routernum; +%} +% +%check($error); +% +%if ($old) { +% @old_psr = $old->part_svc_router; +% foreach my $psr (@old_psr) { +% if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') { +% # do nothing +% } else { +% $error = $psr->delete; +% } +% } +% check($error); +%} +% +%foreach($cgi->param) { +% if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) { +% my $svcpart = $1; +% if(grep {$_->svcpart == $svcpart} @old_psr) { +% # do nothing +% } else { +% my $new_psr = new FS::part_svc_router { svcpart => $svcpart, +% routernum => $routernum }; +% $error = $new_psr->insert; +% } +% check($error); +% } +%} +% +% +%# Yay, everything worked! +%dbh->commit or die dbh->errstr; +%print $cgi->redirect(popurl(3). "browse/router.cgi"); +% +% + diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi new file mode 100755 index 000000000..247a5b446 --- /dev/null +++ b/httemplate/edit/process/svc_acct.cgi @@ -0,0 +1,50 @@ +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find account (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%#unmunge popnum +%$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] ); +% +%#unmunge passwd +%if ( $cgi->param('_password') eq '*HIDDEN*' ) { +% die "fatal: no previous account to recall hidden password from!" unless $old; +% $cgi->param('_password',$old->getfield('_password')); +%} +% +%#unmunge usergroup +%$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] ); +% +%my $new = new FS::svc_acct ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir +% # shell quota slipip) +% } ( fields('svc_acct'), qw( pkgnum svcpart usergroup ) ) +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ); +%} +% +% + diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi new file mode 100755 index 000000000..9e9df7bf0 --- /dev/null +++ b/httemplate/edit/process/svc_acct_pop.cgi @@ -0,0 +1,29 @@ +% +% +%my $popnum = $cgi->param('popnum'); +% +%my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum; +% +%my $new = new FS::svc_acct_pop ( { +% map { +% $_, scalar($cgi->param($_)); +% } fields('svc_acct_pop') +%} ); +% +%my $error = ''; +%if ( $popnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $popnum=$new->getfield('popnum'); +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi"); +%} +% +% + diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi new file mode 100644 index 000000000..cf4604639 --- /dev/null +++ b/httemplate/edit/process/svc_broadband.cgi @@ -0,0 +1,37 @@ +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_broadband', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find broadband service (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%my $new = new FS::svc_broadband ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_broadband'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% $cgi->param('ip_addr', $new->ip_addr); +% print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum ); +%} +% +% + diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi new file mode 100755 index 000000000..773143fe3 --- /dev/null +++ b/httemplate/edit/process/svc_domain.cgi @@ -0,0 +1,32 @@ +% +% +%#remove this to actually test the domains! +%$FS::svc_domain::whois_hack = 1; +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $new = new FS::svc_domain ( { +% map { +% $_, scalar($cgi->param($_)); +% #} qw(svcnum pkgnum svcpart domain action purpose) +% } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) ) +%} ); +% +%my $error = ''; +%if ($cgi->param('svcnum')) { +% $error="Can't modify a domain!"; +%} else { +% $error=$new->insert; +% $svcnum=$new->svcnum; +%} +% +%if ($error) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% + diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi new file mode 100755 index 000000000..97da6ba87 --- /dev/null +++ b/httemplate/edit/process/svc_external.cgi @@ -0,0 +1,30 @@ +% +% +%$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/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi new file mode 100755 index 000000000..3205312f1 --- /dev/null +++ b/httemplate/edit/process/svc_forward.cgi @@ -0,0 +1,30 @@ +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum =$1; +% +%my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::svc_forward ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_forward'), 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_forward.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum"); +%} +% +% + diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html new file mode 100644 index 000000000..44235de63 --- /dev/null +++ b/httemplate/edit/process/svc_phone.html @@ -0,0 +1,4 @@ +<% include( 'elements/svc_Common.html', + 'table' => 'svc_phone', + ) +%> diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi new file mode 100644 index 000000000..e9a52aff2 --- /dev/null +++ b/httemplate/edit/process/svc_www.cgi @@ -0,0 +1,37 @@ +% +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum = $1; +% +%my $old; +%if ( $svcnum ) { +% $old = qsearchs('svc_www', { 'svcnum' => $svcnum } ) +% or die "fatal: can't find website (svcnum $svcnum)!"; +%} else { +% $old = ''; +%} +% +%my $new = new FS::svc_www ( { +% map { +% ($_, scalar($cgi->param($_))); +% #} qw(svcnum pkgnum svcpart recnum usersvc) +% } ( fields('svc_www'), qw( pkgnum svcpart ) ) +%} ); +% +%my $error; +%if ( $svcnum ) { +% $error = $new->replace($old); +%} else { +% $error = $new->insert; +% $svcnum = $new->svcnum; +%} +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ); +%} +% +% + diff --git a/httemplate/edit/rate.cgi b/httemplate/edit/rate.cgi new file mode 100644 index 000000000..72a04c339 --- /dev/null +++ b/httemplate/edit/rate.cgi @@ -0,0 +1,116 @@ +% +% +%my $rate; +%if ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $rate = qsearchs( 'rate', { 'ratenum' => $1 } ); +%} else { #adding +% $rate = new FS::rate {}; +%} +%my $action = $rate->ratenum ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +%my %granularity = ( +% '1', => '1 second', +% '6' => '6 second', +% '30' => '30 second', # '1/2 minute', +% '60' => 'minute', +%); +% +%#my $nous = <<END; +%# WHERE 0 < ( SELECT COUNT(*) FROM rate_prefix +%# WHERE rate_region.regionnum = rate_prefix.regionnum +%# AND countrycode != '1' +%# ) +%#END +% +% + + +<% include("/elements/header.html","$action Rate plan", menubar( + 'Main Menu' => $p, + 'View all rate plans' => "${p}browse/rate.cgi", + )) +%> + +<% include('/elements/progress-init.html', + 'OneTrueForm', + [ 'rate', 'min_', 'sec_' ], + 'process/rate.cgi', + $p.'browse/rate.cgi', + ) +%> +<FORM NAME="OneTrueForm"> +<INPUT TYPE="hidden" NAME="ratenum" VALUE="<% $rate->ratenum %>"> + +Rate plan +<INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="<% $rate->ratename %>"> +<BR><BR> + +<% table() %> +<TR> + <TH>Region</TH> + <TH>Prefix(es)</TH> + <TH><FONT SIZE=-1>Included<BR>minutes</FONT></TH> + <TH><FONT SIZE=-1>Charge per<BR>minute</FONT></TH> + <TH><FONT SIZE=-1>Granularity</FONT></TH> +</TR> +% foreach my $rate_region ( +% sort { lc($a->regionname) cmp lc($b->regionname) } +% qsearch({ +% 'select' => 'DISTINCT ON ( regionnum ) rate_region.*', +% 'table' => 'rate_region', +% 'hashref' => {}, +% #'addl_from' => 'INNER JOIN rate_prefix USING ( regionnum )', +% #'extra_sql' => "WHERE countrycode != '1'", +% +% # 'ORDER BY regionname' +% # ERROR: SELECT DISTINCT ON expressions must +% # match initial ORDER BY expressions +% }) +% ) { +% my $n = $rate_region->regionnum; +% my $rate_detail = +% $rate->dest_detail($rate_region) +% || new FS::rate_detail { 'min_included' => 0, +% 'min_charge' => 0, +% 'sec_granularity' => '60' +% }; +% + + + <TR> + <TD><A HREF="<%$p%>edit/rate_region.cgi?<% $rate_region->regionnum %>"><% $rate_region->regionname %></A></TD> + <TD><% $rate_region->prefixes_short %></TD> + <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> + <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> + <TD> + <SELECT NAME="sec_granularity<%$n%>"> +% foreach my $granularity ( keys %granularity ) { + + <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%> +% } + + </SELECT> + </TR> +% } + + +<TR> + <TD COLSPAN=5 ALIGN="center"> + <A HREF="<%$p%>edit/rate_region.cgi"><I>Add a region</I></A> + </TD> +</TR> + +</TABLE> + +<BR><INPUT NAME="submit" TYPE="button" VALUE="<% + $rate->ratenum ? "Apply changes" : "Add rate plan" +%>" onClick="document.OneTrueForm.submit.disabled=true; process();"> + + </FORM> + </BODY> +</HTML> + diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi new file mode 100644 index 000000000..12cb180de --- /dev/null +++ b/httemplate/edit/rate_region.cgi @@ -0,0 +1,119 @@ +<!-- mason kludge --> +% +% +%my $rate_region; +%if ( $cgi->param('error') ) { +% $rate_region = new FS::rate_region ( { +% map { $_, scalar($cgi->param($_)) } fields('rate_region') +% } ); +%} elsif ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } ); +%} else { #adding +% $rate_region = new FS::rate_region {}; +%} +%my $action = $rate_region->regionnum ? 'Edit' : 'Add'; +% +%my $p1 = popurl(1); +% +%my %granularity = ( +% '6' => '6 second', +% '60' => 'minute', +%); +% +%my @rate_prefix = $rate_region->rate_prefix; +%my $countrycode = ''; +%if ( @rate_prefix ) { +% $countrycode = $rate_prefix[0]->countrycode; +% foreach my $rate_prefix ( @rate_prefix ) { +% eidiot 'multiple country codes per region not yet supported by web UI' +% unless $rate_prefix->countrycode eq $countrycode; +% } +%} +% +% + + +<% include("/elements/header.html","$action Region", menubar( + 'Main Menu' => $p, + #'View all regions' => "${p}browse/rate_region.cgi", + )) +%> +% if ( $cgi->param('error') ) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT><BR> +% } + + +<FORM ACTION="<%$p1%>process/rate_region.cgi" METHOD=POST> + +<INPUT TYPE="hidden" NAME="regionnum" VALUE="<% $rate_region->regionnum %>"> + +<% ntable('#cccccc') %> +<TR> + <TH ALIGN="right">Region name</TH> + <TD><INPUT TYPE="text" NAME="regionname" SIZE=32 VALUE="<% $rate_region->regionname %>"></TR> +</TR> + +<TR> + <TH ALIGN="right">Country code</TH> + <TD><INPUT TYPE="text" NAME="countrycode" SIZE=4 MAXLENGTH=3 VALUE="<% $countrycode %>"></TR> +</TR> + + +<TR> + <TH ALIGN="right">Prefixes</TH> + <TD> + <TEXTAREA NAME="npa" WRAP=SOFT><% join(', ', map $_->npa, @rate_prefix ) %></TEXTAREA> + </TD> +</TR> + +</TABLE> + +<BR> +<% table() %> +<TR> + <TH>Rate plan</TH> + <TH><FONT SIZE=-1>Included<BR>minutes</FONT></TH> + <TH><FONT SIZE=-1>Charge per<BR>minute</FONT></TH> + <TH><FONT SIZE=-1>Granularity</FONT></TH> +</TR> +% foreach my $rate ( qsearch('rate', {}) ) { +% +% my $n = $rate->ratenum; +% my $rate_detail = $rate->dest_detail($rate_region) +% || new FS::rate_region { 'min_included' => 0, +% 'min_charge' => 0, +% 'sec_granularity' => '60' +% }; +% +% + + <TR> + <TD><A HREF="<%$p%>edit/rate.cgi?<% $rate->ratenum %>"><% $rate->ratename %></TD> + <TD><INPUT TYPE="text" SIZE=5 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included %>"></TD> + <TD>$<INPUT TYPE="text" SIZE=4 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>"></TD> + <TD> + <SELECT NAME="sec_granularity<%$n%>"> +% foreach my $granularity ( keys %granularity ) { + + <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%> +% } + + </SELECT> + </TR> +% } + + +</TABLE> + +<BR><BR><INPUT TYPE="submit" VALUE="<% + $rate_region->regionnum ? "Apply changes" : "Add region" +%>"> + + </FORM> + </BODY> +</HTML> + + diff --git a/httemplate/edit/reg_code.cgi b/httemplate/edit/reg_code.cgi new file mode 100644 index 000000000..06bef4879 --- /dev/null +++ b/httemplate/edit/reg_code.cgi @@ -0,0 +1,39 @@ +% +%my $agentnum = $cgi->param('agentnum'); +%$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +%$agentnum = $1; +%my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +% + + +<% include("/elements/header.html",'Generate registration codes for '. $agent->agent, menubar( + 'Main Menu' => $p, + )) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#FF0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<%popurl(1)%>process/reg_code.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true"> +<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>"> + +Generate +<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') %>" SIZE=5 MAXLENGTH=4> +registration codes for <B><% $agent->agent %></B> allowing the following packages: +<BR><BR> +% foreach my $part_pkg ( qsearch('part_pkg', { 'disabled' => '' } ) ) { + + <INPUT TYPE="checkbox" NAME="pkgpart<% $part_pkg->pkgpart %>"> + <% $part_pkg->pkg %> - <% $part_pkg->comment %> + <BR> +% } + + +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="Generate"> + +</FORM></BODY></HTML> + diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi new file mode 100755 index 000000000..0da45c00e --- /dev/null +++ b/httemplate/edit/router.cgi @@ -0,0 +1,78 @@ +<HTML><BODY> +% +% +%my $router; +%if ( $cgi->keywords ) { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% $router = qsearchs('router', { routernum => $1 }) +% or print $cgi->redirect(popurl(2)."browse/router.cgi") ; +%} else { +% $router = new FS::router ( { +% map { $_, scalar($cgi->param($_)) } fields('router') +% } ); +%} +% +%my $routernum = $router->routernum; +%my $action = $routernum ? 'Edit' : 'Add'; +% +%print header("$action Router", menubar( +% 'Main Menu' => "$p", +% '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> +% } + + +<FORM ACTION="<%popurl(1)%>process/router.cgi" METHOD=POST> + <INPUT TYPE="hidden" NAME="table" VALUE="router"> + <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%>"> + +<BR><BR> +Custom fields: +<BR> +<%table() %> +% +%foreach my $field ($router->virtual_fields) { +% print $router->pvf($field)->widget('HTML', 'edit', +% $router->getfield($field)); +%} +% + +</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 => '' }) ) { +% + + <BR> + <INPUT TYPE="checkbox" NAME="svcpart_<%$part_svc->svcpart%>"<% + qsearchs('part_svc_router', { svcpart => $part_svc->svcpart, + routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON"> + <A HREF="<%${p}%>edit/part_svc.cgi?<%$part_svc->svcpart%>"> + <%$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 new file mode 100755 index 000000000..60ca24d3e --- /dev/null +++ b/httemplate/edit/svc_acct.cgi @@ -0,0 +1,428 @@ +% +% +%my $conf = new FS::Conf; +%my @shells = $conf->config('shells'); +% +%my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct, @groups); +%if ( $cgi->param('error') ) { +% +% $svc_acct = new FS::svc_acct ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_acct') +% } ); +% $svcnum = $svc_acct->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% @groups = $cgi->param('radius_usergroup'); +% +%} else { +% +% my($query) = $cgi->keywords; +% if ( $query =~ /^(\d+)$/ ) { #editing +% $svcnum=$1; +% $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_acct) 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 for svcpart $svcpart!" unless $part_svc; +% +% @groups = $svc_acct->radius_groups; +% +% } else { #adding +% +% foreach $_ (split(/-/,$query)) { +% $pkgnum=$1 if /^pkgnum(\d+)$/; +% $svcpart=$1 if /^svcpart(\d+)$/; +% } +% $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry for svcpart $svcpart!" unless $part_svc; +% +% $svc_acct = new FS::svc_acct({svcpart => $svcpart}); +% +% $svcnum=''; +% +% } +%} +% +%my( $cust_pkg, $cust_main ) = ( '', '' ); +%if ( $pkgnum ) { +% $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } ); +% $cust_main = $cust_pkg->cust_main; +%} +% +%unless ( $svcnum || $cgi->param('error') ) { #adding +% +% #set gecos +% if ($cust_main) { +% unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) { +% $svc_acct->setfield('finger', +% $cust_main->getfield('first') . " " . $cust_main->getfield('last') +% ); +% } +% } +% +% $svc_acct->set_default_and_fixed( { +% #false laziness w/svc-acct::_fieldhandlers +% 'usergroup' => sub { +% my( $self, $groups ) = @_; +% if ( ref($groups) eq 'ARRAY' ) { +% @groups = @$groups; +% $groups; +% } elsif ( length($groups) ) { +% @groups = split(/\s*,\s*/, $groups); +% [ @groups ]; +% } else { +% @groups = (); +% []; +% } +% } +% } ); +% +%} +% +%#fixed radius groups always override & display +%if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { +% @groups = split(',', $part_svc->part_svc_column('usergroup')->columnvalue); +%} +% +%my $action = $svcnum ? 'Edit' : 'Add'; +% +%my $svc = $part_svc->getfield('svc'); +% +%my $otaker = getotaker; +% +%my $username = $svc_acct->username; +%my $password; +%if ( $svc_acct->_password ) { +% if ( $conf->exists('showpasswords') || ! $svcnum ) { +% $password = $svc_acct->_password; +% } else { +% $password = "*HIDDEN*"; +% } +%} else { +% $password = ''; +%} +% +%my $ulen = +% $conf->exists('usernamemax') +% ? $conf->config('usernamemax') +% : dbdef->table('svc_acct')->column('username')->length; +%my $ulen2 = $ulen+2; +% +%my $pmax = $conf->config('passwordmax') || 8; +%my $pmax2 = $pmax+2; +% +%my $p1 = popurl(1); +% +% + + +<% include("/elements/header.html","$action $svc account") %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } +% if ( $cust_main ) { + + <% include( '/elements/small_custview.html', $cust_main, '', 1 ) %> + <BR> +% } + + +<FORM NAME="OneTrueForm" ACTION="<% $p1 %>process/svc_acct.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> + +Service # <% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> + +<% ntable("#cccccc",2) %> + +<TR> + <TD ALIGN="right">Service</TD> + <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD> +</TR> + +<TR> + <TD ALIGN="right">Username</TD> + <TD> + <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>> + </TD> +</TR> + +<TR> + <TD ALIGN="right">Password</TD> + <TD> + <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> + (blank to generate) + </TD> +</TR> +% +%my $sec_phrase = $svc_acct->sec_phrase; +%if ( $conf->exists('security_phrase') ) { +% + + + <TR> + <TD ALIGN="right">Security phrase</TD> + <TD> + <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32> + (for forgotten passwords) + </TD> + </TD> +% } else { + + + <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="<% $sec_phrase %>"> +% } +% +%#domain +%my $domsvc = $svc_acct->domsvc || 0; +%if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) { +% + + + <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>"> +% } else { +% +% my %svc_domain = (); +% +% if ( $domsvc ) { +% my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } ); +% if ( $svc_domain ) { +% $svc_domain{$svc_domain->svcnum} = $svc_domain; +% } else { +% warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc"; +% } +% } +% +% if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'D' ) { +% my $svc_domain = qsearchs('svc_domain', { +% 'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue, +% } ); +% if ( $svc_domain ) { +% $svc_domain{$svc_domain->svcnum} = $svc_domain; +% } else { +% warn "unknown svc_domain.svcnum for part_svc_column domsvc: ". +% $part_svc->part_svc_column('domsvc')->columnvalue; +% } +% } +% +% if ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) { +% my @cust_svc = +% map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) } +% qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } ); +% foreach my $cust_svc ( @cust_svc ) { +% my $svc_domain = +% qsearchs('svc_domain', { 'svcnum' => $cust_svc->svcnum } ); +% $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain; +% } +% } else { +% %svc_domain = map { $_->svcnum => $_ } qsearch('svc_domain', {} ); +% } +% +% + + + <TR> + <TD ALIGN="right">Domain</TD> + <TD> + <SELECT NAME="domsvc" SIZE=1> +% foreach my $svcnum ( +% sort { $svc_domain{$a}->domain cmp $svc_domain{$b}->domain } +% keys %svc_domain +% ) { +% my $svc_domain = $svc_domain{$svcnum}; +% + + + <OPTION VALUE="<% $svc_domain->svcnum %>" <% $svc_domain->svcnum == $domsvc ? ' SELECTED' : '' %>><% $svc_domain->domain %> +% } + + </SELECT> + </TD> + </TR> +% } +% +%#pop +%my $popnum = $svc_acct->popnum || 0; +%if ( $part_svc->part_svc_column('popnum')->columnflag eq 'F' ) { +% + + + <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>"> +% } else { + + + <TR> + <TD ALIGN="right">Access number</TD> + <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> + </TR> +% } +% #uid/gid +% foreach my $xid (qw( uid gid )) { +% +% if ( $part_svc->part_svc_column($xid)->columnflag =~ /^[FA]$/ +% || ! $conf->exists("svc_acct-edit_$xid") +% ) { +% +% if ( length($svc_acct->$xid()) ) { + + + <TR> + <TD ALIGN="right"><% uc($xid) %></TD> + <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD> + <TD> + </TD> + </TR> +% } + + + <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>"> +% } else { + + + <TR> + <TD ALIGN="right"><% uc($xid) %></TD> + <TD> + <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>"> + </TD> + </TR> +% } +% } +% +%#finger +%if ( $part_svc->part_svc_column('uid')->columnflag eq 'F' +% && ! $svc_acct->finger ) { +% + + + <INPUT TYPE="hidden" NAME="finger" VALUE=""> +% } else { + + + <TR> + <TD ALIGN="right">GECOS</TD> + <TD> + <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>"> + </TD> + </TR> +% } + + + +<INPUT TYPE="hidden" NAME="dir" VALUE="<% $svc_acct->dir %>"> +% +%#shell +%my $shell = $svc_acct->shell; +%if ( $part_svc->part_svc_column('shell')->columnflag eq 'F' +% || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) +% ) { +% + + + <INPUT TYPE="hidden" NAME="shell" VALUE="<% $shell %>"> +% } else { + + + <TR> + <TD ALIGN="right">Shell</TD> + <TD> + <SELECT NAME="shell" SIZE=1> +% +% my($etc_shell); +% foreach $etc_shell (@shells) { +% + + + <OPTION<% $etc_shell eq $shell ? ' SELECTED' : '' %>><% $etc_shell %> +% } + + + </SELECT> + </TD> + </TR> +% } +% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) { + + + <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>"> +% } else { + + + <TR> + <TD ALIGN="right">Quota:</TD> + <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD> + </TR> +% } +% if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) { + + + <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>"> +% } else { + + + <TR> + <TD ALIGN="right">IP</TD> + <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD> + </TR> +% } +% +%foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) { +% $r =~ /^^r(adius|[cr])_(.+)$/ or next; #? +% my $a = $2; +% +% if ( $part_svc->part_svc_column($r)->columnflag =~ /^[FA]$/ ) { + + + <INPUT TYPE="hidden" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"> +% } else { + + + <TR> + <TD ALIGN="right"><% $FS::raddb::attrib{$a} %></TD> + <TD><INPUT TYPE="text" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"></TD> + </TR> +% } +% } + + + +<TR> + <TD ALIGN="right">RADIUS groups</TD> +% if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) { + + + <TD BGCOLOR="#eeeeee"><% join('<BR>', @groups) %></TD> +% } else { + + + <TD><% FS::svc_acct::radius_usergroup_selector( \@groups ) %></TD> +% } + + +</TR> +% foreach my $field ($svc_acct->virtual_fields) { +% # If the flag is X, it won't even show up in $svc_acct->virtual_fields. +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) { + + + <% $svc_acct->pvf($field)->widget('HTML', 'edit', $svc_acct->getfield($field)) %> +% } +% } + + +</TABLE> +<BR> + +<INPUT TYPE="submit" VALUE="Submit"> + +</FORM></BODY></HTML> diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi new file mode 100755 index 000000000..641aa0378 --- /dev/null +++ b/httemplate/edit/svc_acct_pop.cgi @@ -0,0 +1,57 @@ +<!-- mason kludge --> +% +% +%my $svc_acct_pop; +%if ( $cgi->param('error') ) { +% $svc_acct_pop = new FS::svc_acct_pop ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop') +% } ); +%} elsif ( $cgi->keywords ) { #editing +% my($query)=$cgi->keywords; +% $query =~ /^(\d+)$/; +% $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1}); +%} else { #adding +% $svc_acct_pop = new FS::svc_acct_pop {}; +%} +%my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add'; +%my $hashref = $svc_acct_pop->hashref; +% +%my $p1 = popurl(1); +%print header("$action Access Number", menubar( +% 'Main Menu' => popurl(2), +% 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi", +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/svc_acct_pop.cgi" METHOD=POST>!; +% +%#display +% +%print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$hashref->{popnum}">!, +% "POP #", $hashref->{popnum} ? $hashref->{popnum} : "(NEW)"; +% +%print <<END; +%<PRE> +%City <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="$hashref->{city}"> +%State <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="$hashref->{state}"> +%Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{ac}"> +%Exchange <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{exch}"> +%Local <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="$hashref->{loc}"> +%</PRE> +%END +% +%print qq!<BR><INPUT TYPE="submit" VALUE="!, +% $hashref->{popnum} ? "Apply changes" : "Add Access Number", +% qq!">!; +% +%print <<END; +% </FORM> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi new file mode 100644 index 000000000..2668bf419 --- /dev/null +++ b/httemplate/edit/svc_broadband.cgi @@ -0,0 +1,182 @@ +<!-- mason kludge --> +% +% +%# If it's stupid but it works, it's still stupid. +%# -Kristian +% +% +%use HTML::Widgets::SelectLayers; +%use Tie::IxHash; +% +%my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband ); +%if ( $cgi->param('error') ) { +% $svc_broadband = new FS::svc_broadband ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart) +% } ); +% $svcnum = $svc_broadband->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $svc_broadband->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_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_broadband) 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+)$/; +% } +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart }); +% +% $svcnum=''; +% +% $svc_broadband->set_default_and_fixed; +% +% } +%} +%my $action = $svc_broadband->svcnum ? 'Edit' : 'Add'; +% +%if ($pkgnum) { +% +% #Nothing? +% +%} elsif ( $action eq 'Edit' ) { +% +% #Nothing? +% +%} else { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +%my $p1 = popurl(1); +% +%my ($ip_addr, $speed_up, $speed_down, $blocknum) = +% ($svc_broadband->ip_addr, +% $svc_broadband->speed_up, +% $svc_broadband->speed_down, +% $svc_broadband->blocknum); +% +% + + +<%include("/elements/header.html","Broadband Service $action", '')%> +% if ($cgi->param('error')) { + +<FONT SIZE="+1" COLOR="#ff0000">Error: <%$cgi->param('error')%></FONT><BR> +% } + + +Service #<B><%$svcnum ? $svcnum : "(NEW)"%></B><BR><BR> + +<FORM ACTION="<%${p1}%>process/svc_broadband.cgi" METHOD=POST> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> + <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$pkgnum%>"> + <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$svcpart%>"> + + <%&ntable("#cccccc",2)%> + <TR> + <TD ALIGN="right">IP Address</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { + + <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%$ip_addr%>"><%$ip_addr%> +% } else { + + <INPUT TYPE="text" NAME="ip_addr" VALUE="<%$ip_addr%>"> +% } + + </TD> + </TR> + <TR> + <TD ALIGN="right">Download speed</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { + + <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%$speed_down%>"><%$speed_down%>Kbps +% } else { + + <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%$speed_down%>">Kbps +% } + + </TD> + </TR> + <TR> + <TD ALIGN="right">Upload speed</TD> + <TD BGCOLOR="#ffffff"> +% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { + + <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%$speed_up%>"><%$speed_up%>Kbps +% } else { + + <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%$speed_up%>">Kbps +% } + + </TD> + </TR> +% if ($action eq 'Add') { + + <TR> + <TD ALIGN="right">Router/Block</TD> + <TD BGCOLOR="#ffffff"> + <SELECT NAME="blocknum"> +% +% warn $svc_broadband->svcpart; +% foreach my $router ($svc_broadband->allowed_routers) { +% warn $router->routername; +% foreach my $addr_block ($router->addr_block) { +% + + <OPTION VALUE="<%$addr_block->blocknum%>"<%($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>> + <%$router->routername%>:<%$addr_block->ip_gateway%>/<%$addr_block->ip_netmask%></OPTION> +% +% } +% } +% + + </SELECT> + </TD> + </TR> +% } else { + + + <TR> + <TD ALIGN="right">Router/Block</TD> + <TD BGCOLOR="#ffffff"> + <%$svc_broadband->addr_block->router->routername%>:<%$svc_broadband->addr_block->NetAddr%> + <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$svc_broadband->blocknum%>"> + </TD> + </TR> +% } +% +%foreach my $field ($svc_broadband->virtual_fields) { +% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' && +% $part_svc->part_svc_column($field)->columnflag ne 'X') { +% print $svc_broadband->pvf($field)->widget('HTML', 'edit', +% $svc_broadband->getfield($field)); +% } +%} + + </TABLE> + <BR> + <INPUT TYPE="submit" NAME="submit" VALUE="Submit"> +</FORM> +</BODY> +</HTML> + diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi new file mode 100755 index 000000000..19e0e1285 --- /dev/null +++ b/httemplate/edit/svc_domain.cgi @@ -0,0 +1,90 @@ +% +% +%my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, +% $svc_domain); +%if ( $cgi->param('error') ) { +% $svc_domain = new FS::svc_domain ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_domain') +% } ); +% $svcnum = $svc_domain->svcnum; +% $pkgnum = $cgi->param('pkgnum'); +% $svcpart = $cgi->param('svcpart'); +% $kludge_action = $cgi->param('action'); +% $purpose = $cgi->param('purpose'); +% $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); +% die "No part_svc entry!" unless $part_svc; +%} else { +% $kludge_action = ''; +% $purpose = ''; +% my($query) = $cgi->keywords; +% if ( $query =~ /^(\d+)$/ ) { #editing +% $svcnum=$1; +% $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_domain) 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 +% +% $svc_domain = new FS::svc_domain({}); +% +% foreach $_ (split(/-/,$query)) { +% $pkgnum=$1 if /^pkgnum(\d+)$/; +% $svcpart=$1 if /^svcpart(\d+)$/; +% } +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svcnum=''; +% +% $svc_domain->set_default_and_fixed; +% +% } +% +%} +%my $action = $svcnum ? 'Edit' : 'Add'; +% +%my $svc = $part_svc->getfield('svc'); +% +%my $otaker = getotaker; +% +%my $domain = $svc_domain->domain; +% +%my $p1 = popurl(1); +% +% + + +<% include('/elements/header.html', "$action $svc", '') %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> +% } + + +<FORM ACTION="<% $p1 %>process/svc_domain.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> + +<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>New +<BR> + +<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer + +<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63> + +<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="<% $purpose %>" SIZE=64> + +<P><INPUT TYPE="submit" VALUE="Submit"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi new file mode 100644 index 000000000..1230340ac --- /dev/null +++ b/httemplate/edit/svc_external.cgi @@ -0,0 +1,99 @@ +<!-- 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=''; +% +% $svc_external->set_default_and_fixed; +% +% } +%} +%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 new file mode 100755 index 000000000..73b32dc7b --- /dev/null +++ b/httemplate/edit/svc_forward.cgi @@ -0,0 +1,179 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +% +%my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward); +%if ( $cgi->param('error') ) { +% $svc_forward = new FS::svc_forward ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_forward') +% } ); +% $svcnum = $svc_forward->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_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_forward) 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 +% +% $svc_forward = new FS::svc_forward({}); +% +% foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart +% $pkgnum=$1 if /^pkgnum(\d+)$/; +% $svcpart=$1 if /^svcpart(\d+)$/; +% } +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svcnum=''; +% +% $svc_forward->set_default_and_fixed; +% } +% +%} +%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) +% +% #and including the rest for this customer +% my($u_part_svc,@u_acct_svcparts); +% foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { +% push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% foreach my $i_cust_svc ( +% qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, +% 'svcpart' => $acct_svcpart } ) +% ) { +% my $svc_acct = +% qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); +% $email{$svc_acct->svcnum} = $svc_acct->email; +% } +% } +% } +% +%} elsif ( $action eq 'Add' ) { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +%my($srcsvc,$dstsvc,$dst)=( +% $svc_forward->srcsvc, +% $svc_forward->dstsvc, +% $svc_forward->dst, +%); +%my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; +% +%#display +% +% + + +<% include("/elements/header.html","Mail Forward $action") %> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT> + <BR><BR> +% } + + +Service #<% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR> +Service: <B><% $part_svc->svc %></B><BR><BR> + +<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; + what.form.src.style.backgroundColor = "white"; + } else { + what.form.src.disabled = true; + what.form.src.style.backgroundColor = "lightgrey"; + } +} +function dstchanged(what) { + if ( what.options[what.selectedIndex].value == 0 ) { + what.form.dst.disabled = false; + what.form.dst.style.backgroundColor = "white"; + } else { + what.form.dst.disabled = true; + what.form.dst.style.backgroundColor = "lightgrey"; + } +} +</SCRIPT> + +<% ntable("#cccccc",2) %> +<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> +% } +% 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 STYLE="background-color: lightgrey"' %>> +% } + +</TD></TR> + +<TR><TD ALIGN="right">Forwards to</TD> +<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 STYLE="background-color: lightgrey"' %>> +</TD></TR> + </TABLE> +<BR><INPUT TYPE="submit" VALUE="Submit"> +</FORM> + </BODY> +</HTML> diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi new file mode 100644 index 000000000..ca62b6416 --- /dev/null +++ b/httemplate/edit/svc_phone.cgi @@ -0,0 +1,11 @@ +<% include( 'elements/svc_Common.html', + 'name' => 'Phone number', + 'table' => 'svc_phone', + 'fields' => [qw( countrycode phonenum )], #pin + 'labels' => { + 'countrycode' => 'Country code', + 'phonenum' => 'Phone number', + 'pin' => 'PIN', + }, + ) +%> diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi new file mode 100644 index 000000000..ad69d4db6 --- /dev/null +++ b/httemplate/edit/svc_www.cgi @@ -0,0 +1,216 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +% +%my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www ); +%if ( $cgi->param('error') ) { +% $svc_www = new FS::svc_www ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_www') +% } ); +% $svcnum = $svc_www->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_www=qsearchs('svc_www',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_www) 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_www = new FS::svc_www { svcpart => $svcpart }; +% +% $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +% die "No part_svc entry!" unless $part_svc; +% +% $svcnum=''; +% +% $svc_www->set_default_and_fixed; +% +% } +%} +%my $action = $svc_www->svcnum ? 'Edit' : 'Add'; +% +%my( %svc_acct, %arec ); +%if ($pkgnum) { +% +% my @u_acct_svcparts; +% foreach my $svcpart ( +% map { $_->svcpart } qsearch( 'part_svc', { 'svcdb' => 'svc_acct' } ) +% ) { +% next if $conf->exists('svc_www-usersvc_svcpart') +% && ! grep { $svcpart == $_ } +% $conf->config('svc_www-usersvc_svcpart'); +% push @u_acct_svcparts, $svcpart; +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% my($i_cust_svc); +% foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); +% $svc_acct{$svc_acct->getfield('svcnum')}= +% $svc_acct->cust_svc->part_svc->svc. ': '. $svc_acct->email; +% } +% } +% } +% +% +% my($d_part_svc,@d_acct_svcparts); +% foreach $d_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_domain'}) ) { +% push @d_acct_svcparts,$d_part_svc->getfield('svcpart'); +% } +% +% foreach $i_cust_pkg ( qsearch( 'cust_pkg', { 'custnum' => $custnum } ) ) { +% my $cust_pkgnum = $i_cust_pkg->pkgnum; +% +% foreach my $acct_svcpart (@d_acct_svcparts) { +% +% foreach my $i_cust_svc ( +% qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, +% 'svcpart' => $acct_svcpart } ) +% ) { +% my $svc_domain = +% qsearchs( 'svc_domain', { 'svcnum' => $i_cust_svc->svcnum } ); +% +% my $extra_sql = "AND ( rectype = 'A' OR rectype = 'CNAME' )"; +% unless ( $conf->exists('svc_www-enable_subdomains') ) { +% $extra_sql .= " AND ( reczone = '\@' OR reczone = '". +% $svc_domain->domain. ".' )"; +% } +% +% foreach my $domain_rec ( +% qsearch( 'domain_record', +% { +% 'svcnum' => $svc_domain->svcnum, +% }, +% '', +% $extra_sql, +% ) +% ) { +% $arec{$domain_rec->recnum} = $domain_rec->zone; +% } +% +% if ( $conf->exists('svc_www-enable_subdomains') ) { +% $arec{'www.'. $svc_domain->domain} = 'www.'. $svc_domain->domain +% unless qsearchs( 'domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => 'www', +% } ) +% || qsearchs( 'domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => 'www.'.$svc_domain->domain.'.', +% } ); +% } +% +% $arec{'@.'. $svc_domain->domain} = $svc_domain->domain +% unless qsearchs('domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => '@', +% } ) +% || qsearchs('domain_record', { +% svcnum => $svc_domain->svcnum, +% reczone => $svc_domain->domain.'.', +% } ); +% +% } +% +% } +% } +% +%} elsif ( $action eq 'Edit' ) { +% +% my($domain_rec) = qsearchs('domain_record', { 'recnum'=>$svc_www->recnum }); +% $arec{$svc_www->recnum} = join '.', $domain_rec->recdata, $domain_rec->reczone; +% +%} else { +% die "\$action eq Add, but \$pkgnum is null!\n"; +%} +% +% +%my $p1 = popurl(1); +%print header("Web Hosting $action", ''); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/svc_www.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($recnum,$usersvc)=( +% $svc_www->recnum, +% $svc_www->usersvc, +%); +% +%print &ntable("#cccccc",2), +% '<TR><TD ALIGN="right">Zone</TD><TD><SELECT NAME="recnum" SIZE=1>'; +%foreach $_ (keys %arec) { +% print "<OPTION", $_ eq $recnum ? " SELECTED" : "", +% qq! VALUE="$_">$arec{$_}!; +%} +%print "</SELECT></TD></TR>"; +% +%print '<TR><TD ALIGN="right">Username</TD><TD><SELECT NAME="usersvc" SIZE=1>'; +%print '<OPTION VALUE="">(none)'; +%foreach $_ (keys %svc_acct) { +% print "<OPTION", ($_ eq $usersvc) ? " SELECTED" : "", +% qq! VALUE="$_">$svc_acct{$_}!; +%} +%print "</SELECT></TD></TR>"; +% +%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', +% $svc_www->getfield($field)); +% } +%} +% +%print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">'; +% +%print <<END; +% +% </FORM> +% </BODY> +%</HTML> +%END +% + diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js new file mode 100644 index 000000000..0dbde793d --- /dev/null +++ b/httemplate/elements/calendar-en.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, <mihai_bazon@yahoo.com> +// 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"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// 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-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\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)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +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"; +Calendar._TT["TIME"] = "Time:"; diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js new file mode 100644 index 000000000..b27d9bed0 --- /dev/null +++ b/httemplate/elements/calendar-setup.js @@ -0,0 +1,200 @@ +/* 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.5 2006-02-09 07:18:08 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) + * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc. + * align | alignment (default: "Br"); 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" + * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close + * step | configures the step of the years in drop-down boxes; default: 2 + * position | configures the calendar absolute position; default: null + * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible + * showOthers | if "true" (but default: "false") it will show days from other months too + * + * 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("dateText", null); + param_default("firstDay", null); + param_default("align", "Br"); + 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"); + param_default("electric", true); + param_default("step", 2); + param_default("position", null); + param_default("cache", false); + param_default("showOthers", false); + param_default("multiple", null); + + 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.multiple || 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) { + var p = cal.params; + var update = (cal.dateClicked || p.electric); + if (update && p.inputField) { + p.inputField.value = cal.date.print(p.ifFormat); + if (typeof p.inputField.onchange == "function") + p.inputField.onchange(); + } + if (update && p.displayArea) + p.displayArea.innerHTML = cal.date.print(p.daFormat); + if (update && typeof p.onUpdate == "function") + p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); + }; + + if (params.flat != null) { + if (typeof params.flat == "string") + 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.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; + 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.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } + 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 (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); + if (!(cal && params.cache)) { + window.calendar = cal = new Calendar(params.firstDay, + 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 { + if (params.date) + cal.setDate(params.date); + cal.hide(); + } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } + cal.showsOtherMonths = params.showOthers; + cal.yearStep = params.step; + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.refresh(); + if (!params.position) + cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); + else + cal.showAt(params.position[0], params.position[1]); + return false; + }; + + return cal; +}; diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css new file mode 100644 index 000000000..6f37b7dcd --- /dev/null +++ b/httemplate/elements/calendar-win2k-2.css @@ -0,0 +1,271 @@ +/* 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 tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.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) */ + +.calendar .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: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .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; +} + +.calendar .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..f5c74f608 --- /dev/null +++ b/httemplate/elements/calendar.js @@ -0,0 +1,1806 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * 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.5 2006-02-09 07:18:08 ivan Exp $ + +/** The Calendar object constructor. */ +Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = 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.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. + this.showsOtherMonths = false; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; + // 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) ); + +Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/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 = this.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; +}; + +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. +Calendar.getElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; +}; + +Calendar.getTargetElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; +}; + +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 { + var mcw = mc.offsetWidth; + if (typeof mcw == "undefined") + // Konqueror brain-dead techniques + mcw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "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.innerHTML = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? cal.yearStep : -cal.yearStep; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var ycw = yc.offsetWidth; + if (typeof ycw == "undefined") + // Konqueror brain-dead techniques + ycw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "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 < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = 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, "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.innerHTML; + addEvent(document, "mousemove", tableMouseOver); + } else + addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); + addClass(el, "hilite active"); + 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) == "_") { + el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.innerHTML = 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"); + if (el.calendar) + el.calendar.tooltips.innerHTML = _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") { + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + } + cal.date.setDateOnly(el.caldate); + date = cal.date; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; + // a date was clicked + if (other_month) + cal._init(cal.firstDayOfWeek, date); + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY + // 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 <mihai_bazon@yahoo.com> 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.setFirstDayOfWeek(el.fdow); + return; + case 50: + var range = el._range; + var current = el.innerHTML; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = 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())) { + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } else if (el.navtype == 0) + newdate = closing = true; + } + if (newdate) { + ev && cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + ev && 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; + cell.innerHTML = "<div unselectable='on'>" + text + "</div>"; + 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.innerHTML = Calendar._TT["WK"]; + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + 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); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + 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 = Calendar._TT["TIME"] || " "; + + 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.innerHTML = 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.innerHTML = ":"; + 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 pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.innerHTML, 10); + if (t12) { + if (/pm/i.test(AP.innerHTML) && h < 12) + h += 12; + else if (/am/i.test(AP.innerHTML) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.innerHTML, 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.innerHTML = 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"; + div.appendChild(yr); + } + + this._init(this.firstDayOfWeek, this.date); + parent.appendChild(this.element); +}; + +/** keyboard navigation, only for popup calendars */ +Calendar._keyEvent = function(ev) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) + return false; + (Calendar.is_ie) && (ev = window.event); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; + if (ev.ctrlKey) { + switch (K) { + 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 (K) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.callCloseHandler(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } + break; + } + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); + } + } + break; + case 13: // KEY enter + if (act) + Calendar.cellClick(cal.currentDateEl, ev); + break; + default: + return false; + } + return Calendar.stopEvent(ev); +}; + +/** + * (RE)Initializes the calendar to the given date and firstDayOfWeek + */ +Calendar.prototype._init = function (firstDayOfWeek, date) { + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); + this.table.style.visibility = "hidden"; + 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.firstDayOfWeek = firstDayOfWeek; + this.date = new Date(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + + // calendar voodoo for computing the first day that would actually be + // displayed in the calendar, even if it's from the previous month. + // WARNING: this is magic. ;-) + date.setDate(1); + var day1 = (date.getDay() - this.firstDayOfWeek) % 7; + if (day1 < 0) + day1 += 7; + date.setDate(-day1); + date.setDate(date.getDate() + 1); + + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var ar_days = this.ar_days = new Array(); + var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; + for (var i = 0; i < 6; ++i, row = row.nextSibling) { + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.innerHTML = date.getWeekNumber(); + cell = cell.nextSibling; + } + row.className = "daysrow"; + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); + var wday = date.getDay(); + cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; + var current_month = (date.getMonth() == month); + if (!current_month) { + if (this.showsOtherMonths) { + cell.className += " othermonth"; + cell.otherMonth = true; + } else { + cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + continue; + } + } else { + cell.otherMonth = false; + hasdays = true; + } + cell.disabled = false; + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { + var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + cell.caldate = new Date(date); + cell.ttip = "_"; + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (weekend.indexOf(wday.toString()) != -1) + cell.className += cell.otherMonth ? " oweekend" : " weekend"; + } + } + if (!(hasdays || this.showsOtherMonths)) + row.className = "emptyrow"; + } + this.title.innerHTML = Calendar._MN[month] + ", " + year; + this.onSetTime(); + this.table.style.visibility = "visible"; + this._initMultipleDates(); + // PROFILE + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; +}; + +/** + * 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.firstDayOfWeek, 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.firstDayOfWeek, this.date); +}; + +/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ +Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { + this._init(firstDayOfWeek, 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._dynarch_popupCalendar = 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) { + var calendar = window._dynarch_popupCalendar; + if (!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._dynarch_popupCalendar.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._dynarch_popupCalendar = 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; + } + function fixPosition(box) { + if (box.x < 0) + box.x = 0; + if (box.y < 0) + box.y = 0; + var cp = document.createElement("div"); + var s = cp.style; + s.position = "absolute"; + s.right = s.bottom = s.width = s.height = "0px"; + document.body.appendChild(cp); + var br = Calendar.getAbsolutePos(cp); + document.body.removeChild(cp); + if (Calendar.is_ie) { + br.y += document.body.scrollTop; + br.x += document.body.scrollLeft; + } else { + br.y += window.scrollY; + br.x += window.scrollX; + } + var tmp = box.x + box.width - br.x; + if (tmp > 0) box.x -= tmp; + tmp = box.y + box.height - br.y; + if (tmp > 0) box.y -= tmp; + }; + 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 "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there + } + p.width = w; + p.height = h + 40; + self.monthsCombo.style.display = "none"; + fixPosition(p); + 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) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + 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 = this.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 (this.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"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + 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, "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; + +Date.parseDate = function(str, fmt) { + var today = new Date(); + var y = 0; + var m = -1; + var d = 0; + var a = str.split(/\W+/); + var b = fmt.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%b": + case "%B": + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + 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) + y = today.getFullYear(); + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; +}; + +/** 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, 0, 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 d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var DoW = d.getDay(); + d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu + var ms = d.valueOf(); // GMT + d.setMonth(0); + d.setDate(4); // Thu in Week 1 + return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; +}; + +/** Checks date and time equality */ +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())); +}; + +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + +/** 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 = /%./g; + if (!Calendar.is_ie5 && !Calendar.is_khtml) + return str.replace(re, function (par) { return s[par] || par; }); + + var a = str.match(re); + for (var i = 0; i < a.length; i++) { + var tmp = s[a[i]]; + if (tmp) { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; +}; + +Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; +Date.prototype.setFullYear = function(y) { + var d = new Date(this); + d.__msh_oldSetFullYear(y); + if (d.getMonth() != this.getMonth()) + this.setDate(28); + this.__msh_oldSetFullYear(y); +}; + +// END: DATE OBJECT PATCHES + + +// global object that remembers the calendar +window._dynarch_popupCalendar = null; diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js new file mode 100644 index 000000000..4fe03f1ea --- /dev/null +++ b/httemplate/elements/calendar_stripped.js @@ -0,0 +1,14 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * 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(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=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.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;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_ie5=(Calendar.is_ie&&/msie 5\.0/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=this.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){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};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{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"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.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"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<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=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,"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.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");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)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=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");if(el.calendar)el.calendar.tooltips.innerHTML=_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"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new 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 <mihai_bazon@yahoo.com> 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.setFirstDayOfWeek(el.fdow);return;case 50:var range=el._range;var current=el.innerHTML;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=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;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&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;cell.innerHTML="<div unselectable='on'>"+text+"</div>";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.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);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);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);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=Calendar._TT["TIME"]||" ";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.innerHTML=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.innerHTML=":";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 pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,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.innerHTML=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";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){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(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++y<cal.ar_days.length)ne=cal.ar_days[y][x];else{nextMonth();setVars();}break;}break;}if(ne){if(!ne.disabled)Calendar.cellClick(ne);else if(prev)prevMonth();else nextMonth();}}break;case 13:if(act)Calendar.cellClick(cal.currentDateEl,ev);break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(firstDayOfWeek,date){var today=new Date(),TY=today.getFullYear(),TM=today.getMonth(),TD=today.getDate();this.table.style.visibility="hidden";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.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&¤t_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,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._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!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._dynarch_popupCalendar.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._dynarch_popupCalendar=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;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};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 "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);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){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;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=this.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(this.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";}}}};Calendar.prototype._displayWeekdays=function(){var fdow=this.firstDayOfWeek;var cell=this.firstdayname;var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<7;++i){cell.className="day name";var realday=(i+fdow)%7;if(i){cell.ttip=Calendar._TT["DAY_FIRST"].replace("%s",Calendar._DN[realday]);cell.navtype=100;cell.calendar=this;cell.fdow=realday;Calendar._add_evs(cell);}if(weekend.indexOf(realday.toString())!=-1){Calendar.addClass(cell,"weekend");}cell.innerHTML=Calendar._SDN[(i+fdow)%7];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,"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.parseDate=function(str,fmt){var today=new Date();var y=0;var m=-1;var d=0;var a=str.split(/\W+/);var b=fmt.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(!a[i])continue;switch(b[i]){case "%d":case "%e":d=parseInt(a[i],10);break;case "%m":m=parseInt(a[i],10)-1;break;case "%Y":case "%y":y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);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)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};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,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};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.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};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=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i<a.length;i++){var tmp=s[a[i]];if(tmp){re=new RegExp(a[i],'g');str=str.replace(re,tmp);}}return str;};Date.prototype.__msh_oldSetFullYear=Date.prototype.setFullYear;Date.prototype.setFullYear=function(y){var d=new Date(this);d.__msh_oldSetFullYear(y);if(d.getMonth()!=this.getMonth())this.setDate(28);this.__msh_oldSetFullYear(y);};window._dynarch_popupCalendar=null;
\ No newline at end of file diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html new file mode 100644 index 000000000..0a92e4548 --- /dev/null +++ b/httemplate/elements/checkboxes-table-name.html @@ -0,0 +1,85 @@ +% +% +% ## +% # required +% ## +% # 'link_table' => 'table_name', +% # +% # 'name_col' => 'name_column', +% # #or +% # 'name_callback' => sub { }, +% # +% # 'names_list' => [ 'value', 'other value' ], +% # +% ## +% # recommended (required?) +% ## +% # 'source_obj' => $obj, +% # #or? +% # #'source_table' => 'table_name', +% # #'sourcenum' => '4', #current value of primary key in source_table +% # # # (none is okay, just pass it if you have it) +% ## +% # optional +% ## +% # 'num_col' => 'col_name' #if column name is different in link_table than +% # #source_table +% # 'link_static' => { 'column' => 'value' }, +% +% my( %opt ) = @_; +% +% my( $source_pkey, $sourcenum, $source_obj ); +% if ( $opt{'source_obj'} ) { +% +% $source_obj = $opt{'source_obj'}; +% #$source_table = $source_obj->dbdef_table->table; +% $source_pkey = $source_obj->dbdef_table->primary_key; +% $sourcenum = $source_obj->$source_pkey(); +% +% } else { +% +% #$source_obj? +% $source_pkey = $opt{'source_table'} +% ? dbdef->table($opt{'source_table'})->primary_key +% : ''; +% $sourcenum = $opt{'sourcenum'}; +% } +% +% $source_pkey = $opt{'num_col'} || $source_pkey; +% +% my $link_static = $opt{'link_static'} || {}; +% +% +% foreach my $name ( @{ $opt{'names_list'} } ) { +% +% my $checked; +% if ( $cgi->param('error') ) { +% +% $checked = $cgi->param($opt{'link_table'}. ".$name" ) +% ? 'CHECKED' +% : ''; +% +% } else { +% +% $checked = +% qsearchs( $opt{'link_table'}, { +% $source_pkey => $sourcenum, +% $opt{'name_col'} => $name, +% %$link_static, +% } ) +% ? 'CHECKED' +% : '' +% +% } +% +% + + + <INPUT TYPE="checkbox" NAME="<% $opt{'link_table'}. ".$name" %>" <% $checked %> VALUE="ON"> + + <% $name %> + + <BR> +% } + + diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html new file mode 100644 index 000000000..cdfa58eca --- /dev/null +++ b/httemplate/elements/checkboxes-table.html @@ -0,0 +1,123 @@ +% +% +% ## +% # required +% ## +% # 'target_table' => 'table_name', +% # 'link_table' => 'table_name', +% # +% # 'name_col' => 'name_column', +% # #or +% # 'name_callback' => sub { }, +% # +% ## +% # recommended (required?) +% ## +% # 'source_obj' => $obj, +% # #or? +% # #'source_table' => 'table_name', +% # #'sourcenum' => '4', #current value of primary key in source_table +% # # # (none is okay, just pass it if you have it) +% ## +% # optional +% ## +% # 'disable-able' => 1, +% +% my( %opt ) = @_; +% +% my $target_pkey = dbdef->table($opt{'target_table'})->primary_key; +% +% my( $source_pkey, $sourcenum, $source_obj ); +% if ( $opt{'source_obj'} ) { +% +% $source_obj = $opt{'source_obj'}; +% #$source_table = $source_obj->dbdef_table->table; +% $source_pkey = $source_obj->dbdef_table->primary_key; +% $sourcenum = $source_obj->$source_pkey(); +% +% } else { +% +% #$source_obj? +% $source_pkey = $opt{'source_table'} +% ? dbdef->table($opt{'source_table'})->primary_key +% : ''; +% $sourcenum = $opt{'sourcenum'}; +% } +% +% my $hashref = $opt{'hashref'} || {}; +% +% my $extra_sql = ''; +% +% if ( $opt{'disable-able'} ) { +% $hashref->{'disabled'} = ''; +% +% $extra_sql .= ( $sourcenum && $source_pkey ) +% ? "OR $source_pkey = $sourcenum" +% : ''; +% } +% +% +% foreach my $target_obj ( +% qsearch({ 'table' => $opt{'target_table'}, +% 'hashref' => $hashref, +% 'select' => $opt{'target_table'}. '.*', +% 'addl_from' => "LEFT JOIN $opt{'link_table'} USING ( $target_pkey )", +% 'extra_sql' => $extra_sql, +% }) +% ) { +% +% my $targetnum = $target_obj->$target_pkey(); +% +% my $checked; +% if ( $cgi->param('error') ) { +% +% $checked = $cgi->param($target_pkey.$targetnum) +% ? 'CHECKED' +% : ''; +% +% } else { +% +% $checked = qsearchs( $opt{'link_table'}, { +% $source_pkey => $sourcenum, +% $target_pkey => $targetnum, +% } ) +% ? 'CHECKED' +% : '' +% +% } +% +% + + + <INPUT TYPE="checkbox" NAME="<% $target_pkey. $targetnum %>" <% $checked %> VALUE="ON"> +% if ( $opt{'target_link'} ) { + + + <A HREF="<% $opt{'target_link'} %><% $targetnum %>"> +% +% +% } +% +<% $targetnum %>: +% if ( $opt{'name_callback'} ) { + + + <% &{ $opt{'name_callback'} }( $target_obj ) %><% $opt{'target_link'} ? '</A>' : '' %> +% } else { +% my $name_col = $opt{'name_col'}; +% + + + <% $target_obj->$name_col() %><% $opt{'target_link'} ? '</A>' : '' %> +% } +% if ( $opt{'disable-able'} ) { + + + <% $target_obj->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> +% } + + + <BR> +% } + + diff --git a/httemplate/elements/cssexpr.js b/httemplate/elements/cssexpr.js new file mode 100644 index 000000000..c434d8da0 --- /dev/null +++ b/httemplate/elements/cssexpr.js @@ -0,0 +1,66 @@ +function constExpression(x) { + return x; +} + +function simplifyCSSExpression() { + try { + var ss,sl, rs, rl; + ss = document.styleSheets; + sl = ss.length + + for (var i = 0; i < sl; i++) { + simplifyCSSBlock(ss[i]); + } + } + catch (exc) { + //alert("Got an error while processing css. The page should still work but might be a bit slower"); + throw exc; + } +} + +function simplifyCSSBlock(ss) { + var rs, rl; + + for (var i = 0; i < ss.imports.length; i++) + simplifyCSSBlock(ss.imports[i]); + + if (ss.cssText.indexOf("expression(constExpression(") == -1) + return; + + rs = ss.rules; + rl = rs.length; + for (var j = 0; j < rl; j++) + simplifyCSSRule(rs[j]); + +} + +function simplifyCSSRule(r) { + var str = r.style.cssText; + var str2 = str; + var lastStr; + do { + lastStr = str2; + str2 = simplifyCSSRuleHelper(lastStr); + } while (str2 != lastStr) + + if (str2 != str) + r.style.cssText = str2; +} + +function simplifyCSSRuleHelper(str) { + var i, i2; + i = str.indexOf("expression(constExpression("); + if (i == -1) return str; + i2 = str.indexOf("))", i); + var hd = str.substring(0, i); + var tl = str.substring(i2 + 2); + var exp = str.substring(i + 27, i2); + var val = eval(exp) + return hd + val + tl; +} + +if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) { + window.attachEvent("onload", function () { + simplifyCSSExpression(); + }); +} diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html new file mode 100644 index 000000000..32d121996 --- /dev/null +++ b/httemplate/elements/footer.html @@ -0,0 +1,5 @@ + </TD> + </TR> + </TABLE> + </BODY> +</HTML> diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css new file mode 100644 index 000000000..6f7cadb2e --- /dev/null +++ b/httemplate/elements/freeside.css @@ -0,0 +1,15 @@ +* { + font-family: Arial, Verdana, Helvetica, sans-serif; +} + +A:link IMG, A:visited { border-style: none } +/* A:focus {text-decoration: underline } */ + +a:link, a:visited { + /* text-decoration: none; */ + color: #000000; +} +/* a:hover { text-decoration: underline } */ + +/* a:focus { background-color: #ccccee } */ + diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html new file mode 100644 index 000000000..43d9bc3af --- /dev/null +++ b/httemplate/elements/header-popup.html @@ -0,0 +1,23 @@ +% +% my($title, $menubar) = ( shift, shift ); #$menubar is unused here though +% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. +% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section +% my $conf = new FS::Conf; +% + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<HTML> + <HEAD> + <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 %> + </HEAD> + <BODY BGCOLOR="#e8e8e8" <% $etc %>> + <FONT SIZE=6> + <CENTER><% $title %></CENTER> + </FONT> + <BR><!--<BR>--> diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html new file mode 100644 index 000000000..ca74ca5b9 --- /dev/null +++ b/httemplate/elements/header.html @@ -0,0 +1,198 @@ +% +% my($title, $menubar) = ( shift, shift ); +% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. +% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section +% my $conf = new FS::Conf; +% +% + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<HTML> + <HEAD> + <TITLE> + <% $title %> + </TITLE> + <META HTTP-Equiv="Cache-Control" Content="no-cache"> + <META HTTP-Equiv="Pragma" Content="no-cache"> + <META HTTP-Equiv="Expires" Content="0"> + + <% include('menu.html', 'freeside_baseurl' => $fsurl ) %> + + <SCRIPT TYPE="text/javascript"> + function clearhint_search_cust (what) { + if ( what.value == '(cust #, name, company or phone)' ) + what.value = ''; + } + + function clearhint_search_invoice (what) { + if ( what.value == '(inv #)' ) + what.value = ''; + } + + function clearhint_search_svc (what) { + if ( what.value == '(user, user@domain or domain)' ) + what.value = ''; + } + + function clearhint_search_ticket (what) { + if ( what.value == '(ticket # or subject string)' ) + what.value = ''; + } + </SCRIPT> + + <% $head %> + + </HEAD> + <BODY BACKGROUND="<%$fsurl%>images/background-cheat.png" <% $etc %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"> + <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0; padding-right:4"> + <tr> + <td rowspan=2 BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" SRC="<%$fsurl%>images/small-logo.png"></td> + <td align=left rowspan=2 BGCOLOR="#ffffff"> <!-- valign="top" --> + <font size=6><% $conf->config('company_name') || 'ExampleCo' %></font> + </td> + <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %> </b><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/XXXwritethis">Preferences</a> <BR></FONT> + </td> + </tr> + <tr> + <td align=right valign=bottom BGCOLOR="#ffffff"> + + <table> + <tr> + <td align=right BGCOLOR="#ffffff"> + <FONT SIZE="-2"> + <A HREF="http://www.sisd.com/freeside">Freeside</A> v<% $FS::VERSION %><BR> + <A HREF="<% $fsurl %>docs/">Documentation</A><BR> + </FONT> + </td> +% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { +% eval "use RT;"; + + <td bgcolor=#000000></td> + <td align=left> + <FONT SIZE="-2"> + <A HREF="http://www.bestpractical.com/rt">RT<A> v<% $RT::VERSION %><BR> + <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR> + </FONT> + </td> +% } + + + </tr> + </table> + + </td> + </tr> + </table> + +<style type="text/css"> +input.fsblackbutton { + background-color:#333333; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666') +} + +input.fsblackbuttonselected { + background-color:#7e0079; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079') +} +</style> + + <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0> + <TR> + <TD COLSPAN=5 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD> + </TR> + + <TR> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>edit/cust_main.cgi" METHOD="GET" STYLE="margin:0"> + <INPUT TYPE="submit" VALUE="New customer" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="vertical-align:bottom"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A HREF="<%$fsurl%>search/cust_main.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search customers" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> +% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) { + + <FORM ACTION="<%$fsurl%>search/cust_bill.html" METHOD="GET" STYLE="margin:0;display:inline"> + <INPUT NAME="invnum" TYPE="text" VALUE="(inv #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px"> +% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { + + <A HREF="<%$fsurl%>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A> +% } + + <BR> + <INPUT TYPE="submit" VALUE="Search invoices" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> +% } + + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right"> + <FORM ACTION="<%$fsurl%>search/svc_Smart.html" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="search_svc" TYPE="text" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A HREF="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search services"CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%"> + </FORM> + </TD> + + <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right" STYLE="padding-right:4px"> + <FORM ACTION="<%$fsurl%>rt/index.html" METHOD="GET" STYLE="margin:0"> + <INPUT NAME="q" TYPE="text" VALUE="(ticket # or subject string)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="vertical-align:bottom;text-align:right"><BR> + <A HREF="<%$fsurl%>rt/Search/Build.html" STYLE="color: #ffffff; font-size: 70%">Advanced</A> + <INPUT TYPE="submit" VALUE="Search tickets" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px"> + </FORM> + </TD> + + </TR> + </TABLE> + <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4> + <TR> + <TD BGCOLOR="#000000" STYLE="padding:0" WIDTH="154"></TD> + <TD STYLE="padding:0" WIDTH="13"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-corner.png"></TD> + <TD STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD> + </TR> + <TR HEIGHT="100%"> + <TD BGCOLOR="#000000" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar); + </SCRIPT> + <BR> + <IMG SRC="<%$fsurl%>images/32clear.gif" HEIGHT="1" WIDTH="154"> + + </TD> + <TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-side.png"></TD> + <TD BGCOLOR="#e8e8e8" HEIGHT="100%"> <!-- WIDTH="100%"> --> + + <FONT SIZE=6> + <% $title %> + </FONT> + + <BR><BR> + <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %> diff --git a/httemplate/elements/iframecontentmws.js b/httemplate/elements/iframecontentmws.js new file mode 100644 index 000000000..c80998957 --- /dev/null +++ b/httemplate/elements/iframecontentmws.js @@ -0,0 +1,20 @@ +/*
+ iframecontentmws.js - Foteos Macrides
+ Initial: October 10, 2004 - Last Revised: May 9, 2005
+ Simple script for using an HTML file as iframe content in overlibmws popups.
+ Include WRAP and TEXTPADDING,0 in the overlib call to ensure that the width
+ arg is respected (unless the CAPTION plus CLOSETEXT widths add up to more than
+ the width arg, in which case you should increase the width arg). The name arg
+ should be a unique string for each popup with iframe content in the document.
+ The frameborder arg should be 1 (browser default if omitted) or 0.
+
+ See http://www.macridesweb.com/oltest/IFRAME.html for demonstration.
+*/
+
+function OLiframeContent(src, width, height, name, frameborder) {
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+ +(name!=null?' name="'+name+'" id="'+name+'"':'')
+ +(frameborder!=null?' frameborder="'+frameborder+'"':'')
+ +' scrolling="auto">'
+ +'<div>[iframe not supported]</div></iframe>');
+}
diff --git a/httemplate/elements/jsrsClient.js b/httemplate/elements/jsrsClient.js new file mode 100644 index 000000000..3a2572ccb --- /dev/null +++ b/httemplate/elements/jsrsClient.js @@ -0,0 +1,356 @@ +// +// jsrsClient.js - javascript remote scripting client include +// +// Author: Brent Ashley [jsrs@megahuge.com] +// +// make asynchronous remote calls to server without client page refresh +// +// see license.txt for copyright and license information + +/* +see history.txt for full history +2.0 26 Jul 2001 - added POST capability for IE/MOZ +2.2 10 Aug 2003 - added Opera support +2.3(beta) 10 Oct 2003 - added Konqueror support - **needs more testing** +*/ + +// callback pool needs global scope +var jsrsContextPoolSize = 0; +var jsrsContextMaxPool = 10; +var jsrsContextPool = new Array(); +var jsrsBrowser = jsrsBrowserSniff(); +var jsrsPOST = true; +var containerName; + +// constructor for context object +function jsrsContextObj( contextID ){ + + // properties + this.id = contextID; + this.busy = true; + this.callback = null; + this.container = contextCreateContainer( contextID ); + + // methods + this.GET = contextGET; + this.POST = contextPOST; + this.getPayload = contextGetPayload; + this.setVisibility = contextSetVisibility; +} + +// method functions are not privately scoped +// because Netscape's debugger chokes on private functions +function contextCreateContainer( containerName ){ + // creates hidden container to receive server data + var container; + switch( jsrsBrowser ) { + case 'NS': + container = new Layer(100); + container.name = containerName; + container.visibility = 'hidden'; + container.clip.width = 100; + container.clip.height = 100; + break; + + case 'IE': + document.body.insertAdjacentHTML( "afterBegin", '<span id="SPAN' + containerName + '"></span>' ); + var span = document.all( "SPAN" + containerName ); + var html = '<iframe name="' + containerName + '" src=""></iframe>'; + span.innerHTML = html; + span.style.display = 'none'; + container = window.frames[ containerName ]; + break; + + case 'MOZ': + var span = document.createElement('SPAN'); + span.id = "SPAN" + containerName; + document.body.appendChild( span ); + var iframe = document.createElement('IFRAME'); + iframe.name = containerName; + iframe.id = containerName; + span.appendChild( iframe ); + container = iframe; + break; + + case 'OPR': + var span = document.createElement('SPAN'); + span.id = "SPAN" + containerName; + document.body.appendChild( span ); + var iframe = document.createElement('IFRAME'); + iframe.name = containerName; + iframe.id = containerName; + span.appendChild( iframe ); + container = iframe; + break; + + case 'KONQ': + var span = document.createElement('SPAN'); + span.id = "SPAN" + containerName; + document.body.appendChild( span ); + var iframe = document.createElement('IFRAME'); + iframe.name = containerName; + iframe.id = containerName; + span.appendChild( iframe ); + container = iframe; + + // Needs to be hidden for Konqueror, otherwise it'll appear on the page + span.style.display = none; + iframe.style.display = none; + iframe.style.visibility = hidden; + iframe.height = 0; + iframe.width = 0; + + break; + } + return container; +} + +function contextPOST( rsPage, func, parms ){ + + var d = new Date(); + var unique = d.getTime() + '' + Math.floor(1000 * Math.random()); + var doc = (jsrsBrowser == "IE" ) ? this.container.document : this.container.contentDocument; + doc.open(); + doc.write('<html><body>'); + doc.write('<form name="jsrsForm" method="post" target="" '); + doc.write(' action="' + rsPage + '?U=' + unique + '">'); + doc.write('<input type="hidden" name="C" value="' + this.id + '">'); + + // func and parms are optional + if (func != null){ + doc.write('<input type="hidden" name="F" value="' + func + '">'); + + if (parms != null){ + if (typeof(parms) == "string"){ + // single parameter + doc.write( '<input type="hidden" name="P0" ' + + 'value="[' + jsrsEscapeQQ(parms) + ']">'); + } else { + // assume parms is array of strings + for( var i=0; i < parms.length; i++ ){ + doc.write( '<input type="hidden" name="P' + i + '" ' + + 'value="[' + jsrsEscapeQQ(parms[i]) + ']">'); + } + } // parm type + } // parms + } // func + + doc.write('</form></body></html>'); + doc.close(); + doc.forms['jsrsForm'].submit(); +} + +function contextGET( rsPage, func, parms ){ + + // build URL to call + var URL = rsPage; + + // always send context + URL += "?C=" + this.id; + + // func and parms are optional + if (func != null){ + URL += "&F=" + escape(func); + + if (parms != null){ + if (typeof(parms) == "string"){ + // single parameter + URL += "&P0=[" + escape(parms+'') + "]"; + } else { + // assume parms is array of strings + for( var i=0; i < parms.length; i++ ){ + URL += "&P" + i + "=[" + escape(parms[i]+'') + "]"; + } + } // parm type + } // parms + } // func + + // unique string to defeat cache + var d = new Date(); + URL += "&U=" + d.getTime(); + + // make the call + switch( jsrsBrowser ) { + case 'NS': + this.container.src = URL; + break; + case 'IE': + this.container.document.location.replace(URL); + break; + case 'MOZ': + this.container.src = ''; + this.container.src = URL; + break; + case 'OPR': + this.container.src = ''; + this.container.src = URL; + break; + case 'KONQ': + this.container.src = ''; + this.container.src = URL; + break; + } +} + +function contextGetPayload(){ + switch( jsrsBrowser ) { + case 'NS': + return this.container.document.forms['jsrs_Form'].elements['jsrs_Payload'].value; + case 'IE': + return this.container.document.forms['jsrs_Form']['jsrs_Payload'].value; + case 'MOZ': + return window.frames[this.container.name].document.forms['jsrs_Form']['jsrs_Payload'].value; + case 'OPR': + var textElement = window.frames[this.container.name].document.getElementById("jsrs_Payload"); + case 'KONQ': + var textElement = window.frames[this.container.name].document.getElementById("jsrs_Payload"); + return textElement.value; + } +} + +function contextSetVisibility( vis ){ + switch( jsrsBrowser ) { + case 'NS': + this.container.visibility = (vis)? 'show' : 'hidden'; + break; + case 'IE': + document.all("SPAN" + this.id ).style.display = (vis)? '' : 'none'; + break; + case 'MOZ': + document.getElementById("SPAN" + this.id).style.visibility = (vis)? '' : 'hidden'; + case 'OPR': + document.getElementById("SPAN" + this.id).style.visibility = (vis)? '' : 'hidden'; + this.container.width = (vis)? 250 : 0; + this.container.height = (vis)? 100 : 0; + break; + } +} + +// end of context constructor + +function jsrsGetContextID(){ + var contextObj; + for (var i = 1; i <= jsrsContextPoolSize; i++){ + contextObj = jsrsContextPool[ 'jsrs' + i ]; + if ( !contextObj.busy ){ + contextObj.busy = true; + return contextObj.id; + } + } + // if we got here, there are no existing free contexts + if ( jsrsContextPoolSize <= jsrsContextMaxPool ){ + // create new context + var contextID = "jsrs" + (jsrsContextPoolSize + 1); + jsrsContextPool[ contextID ] = new jsrsContextObj( contextID ); + jsrsContextPoolSize++; + return contextID; + } else { + alert( "jsrs Error: context pool full" ); + return null; + } +} + +function jsrsExecute( rspage, callback, func, parms, visibility ){ + // call a server routine from client code + // + // rspage - href to asp file + // callback - function to call on return + // or null if no return needed + // (passes returned string to callback) + // func - sub or function name to call + // parm - string parameter to function + // or array of string parameters if more than one + // visibility - optional boolean to make container visible for debugging + + // get context + var contextObj = jsrsContextPool[ jsrsGetContextID() ]; + contextObj.callback = callback; + + var vis = (visibility == null)? false : visibility; + contextObj.setVisibility( vis ); + + if ( jsrsPOST && ((jsrsBrowser == 'IE') || (jsrsBrowser == 'MOZ'))){ + contextObj.POST( rspage, func, parms ); + } else { + contextObj.GET( rspage, func, parms ); + } + + return contextObj.id; +} + +function jsrsLoaded( contextID ){ + // get context object and invoke callback + var contextObj = jsrsContextPool[ contextID ]; + if( contextObj.callback != null){ + contextObj.callback( jsrsUnescape( contextObj.getPayload() ), contextID ); + } + // clean up and return context to pool + contextObj.callback = null; + contextObj.busy = false; +} + +function jsrsError( contextID, str ){ + alert( unescape(str) ); + jsrsContextPool[ contextID ].busy = false +} + +function jsrsEscapeQQ( thing ){ + return thing.replace(/'"'/g, '\\"'); +} + +function jsrsUnescape( str ){ + // payload has slashes escaped with whacks + return str.replace( /\\\//g, "/" ); +} + +function jsrsBrowserSniff(){ + if (document.layers) return "NS"; + if (document.all) { + // But is it really IE? + // convert all characters to lowercase to simplify testing + var agt=navigator.userAgent.toLowerCase(); + var is_opera = (agt.indexOf("opera") != -1); + var is_konq = (agt.indexOf("konqueror") != -1); + if(is_opera) { + return "OPR"; + } else { + if(is_konq) { + return "KONQ"; + } else { + // Really is IE + return "IE"; + } + } + } + if (document.getElementById) return "MOZ"; + return "OTHER"; +} + +///////////////////////////////////////////////// +// +// user functions + +function jsrsArrayFromString( s, delim ){ + // rebuild an array returned from server as string + // optional delimiter defaults to ~ + var d = (delim == null)? '~' : delim; + return s.split(d); +} + +function jsrsDebugInfo(){ + // use for debugging by attaching to f1 (works with IE) + // with onHelp = "return jsrsDebugInfo();" in the body tag + var doc = window.open().document; + doc.open; + doc.write( 'Pool Size: ' + jsrsContextPoolSize + '<br><font face="arial" size="2"><b>' ); + for( var i in jsrsContextPool ){ + var contextObj = jsrsContextPool[i]; + doc.write( '<hr>' + contextObj.id + ' : ' + (contextObj.busy ? 'busy' : 'available') + '<br>'); + doc.write( contextObj.container.document.location.pathname + '<br>'); + doc.write( contextObj.container.document.location.search + '<br>'); + doc.write( '<table border="1"><tr><td>' + contextObj.container.document.body.innerHTML + '</td></tr></table>' ); + } + doc.write('</table>'); + doc.close(); + return false; +} diff --git a/httemplate/elements/jsrsServer.html b/httemplate/elements/jsrsServer.html new file mode 100644 index 000000000..f37b0aaee --- /dev/null +++ b/httemplate/elements/jsrsServer.html @@ -0,0 +1,4 @@ +% +% my $server = new FS::UI::Web::JSRPC '', $cgi; +% +<% $server->process %> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html new file mode 100644 index 000000000..14c471d58 --- /dev/null +++ b/httemplate/elements/menu.html @@ -0,0 +1,346 @@ +<script type="text/javascript" src="<%$fsurl%>elements/cssexpr.js"></script> +<script type="text/javascript" src="<%$fsurl%>elements/xmenu.js"></script> +<link href="<%$fsurl%>elements/xmenu.css" type="text/css" rel="stylesheet"> +<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet"> + +<SCRIPT TYPE="text/javascript"> + + webfxMenuImagePath = "<%$fsurl%>images/"; + webfxMenuUseHover = 1; + webfxMenuShowTime = 300; + webfxMenuHideTime = 500; + + var myBar = new WebFXMenuBar; + +% foreach my $item ( keys %menu ) { +% +% my( $url_or_submenu, $tooltip ) = @{ $menu{$item} }; +% +% if ( ref($url_or_submenu) ) { +% +% #warn $item; +% +% my( $subhtml, $submenuname ) = submenu($url_or_submenu, $item); + + <% $subhtml %> + myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname %> )); + +% } else { + + myBar.add(new WebFXMenuButton("<% $item %>", "<% $url_or_submenu %>", "<% $tooltip %>" )); + +% } +% +% } + + myBar.show( null, 'vertical' ); + myBar.width = 154; + +</SCRIPT> + +<%init> +my( %opt ) = @_; +my $conf = new FS::Conf; +my $fsurl = $opt{'freeside_baseurl'}; + +my $curuser = $FS::CurrentUser::CurrentUser; + +#Active tickets not assigned to a customer + +tie my %report_customers_lists, 'Tie::IxHash', + 'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ], + 'by last name' => [ $fsurl. 'search/cust_main.cgi?browse=last', '' ], + 'by company name' => [ $fsurl. 'search/cust_main.cgi?browse=company', '' ], +; +$report_customers_lists{'by active trouble tickets'} = [ $fsurl. 'search/cust_main.cgi?browse=tickets', '' ] + if $conf->config('ticket_system'); + +tie my %report_customers_search, 'Tie::IxHash', + 'by ordering employee' => [ $fsurl. 'search/cust_main-otaker.cgi' ], +; + +tie my %report_customers, 'Tie::IxHash', + 'List customers' => [ \%report_customers_lists, 'List customers' ], + 'Search customers' => [ \%report_customers_search, 'Search customers' ], + 'Zip code distribution' => [ $fsurl.'search/report_cust_main-zip.html', 'Zip codes by number of customers' ], +; + +tie my %report_invoices_open, 'Tie::IxHash', + 'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ], + '15 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN15_date', 'Invoices 15 days or older with an unpaid balance' ], + '30 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN30_date', 'Invoices 30 days or older with an unpaid balance' ], + '60 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN60_date', 'Invoices 60 days or older with an unpaid balance' ], + '90 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN90_date', 'Invoices 90 days or older with an unpaid balance' ], + '120 day open invoices' => [ $fsurl.'search/cust_bill.html?OPEN120_date', 'Invoices 120 days or older with an unpaid balance' ], +; + +tie my %report_invoices, 'Tie::IxHash', + 'Open invoices' => [ \%report_invoices_open, 'Open invoices' ], + 'All invoices' => [ $fsurl. 'search/cust_bill.html?date', 'List all invoices' ], + 'Advanced invoice reports' => [ $fsurl.'search/report_cust_bill.html', 'by agent, date range, etc.' ], +; + +tie my %report_services_acct, 'Tie::IxHash', + 'All accounts by username' => [ $fsurl.'search/svc_acct.cgi?username', '' ], + 'All accounts by UID' => [ $fsurl.'search/svc_acct.cgi?uid', '' ], +; +$report_services_acct{'Unlinked accounts'} = [ $fsurl.'search/svc_acct.cgi?UN_uid', 'Pre-Freeside accounts without a customer record' ] + if $curuser->access_right('View/link unlinked services'); + +tie my %report_services_domain, 'Tie::IxHash', + 'All domains' => [ $fsurl.'search/svc_domain.cgi?domain', '' ], +; +$report_services_domain{'Unlinked domains'} = [ $fsurl.'search/svc_domain.cgi?UN_domain', 'Pre-Freeside domains without a customer record' ] + if $curuser->access_right('View/link unlinked services'); + +tie my %report_services_forward, 'Tie::IxHash', + 'All mail forwards' => [ $fsurl.'search/svc_forward.cgi?svcnum', '' ], +; +$report_services_forward{'Unlinked mail forwards'} = [ $fsurl.'search/svc_forward.cgi?UN_svcnum', 'Pre-Freeside mail forwards without a customer record' ] + if $curuser->access_right('View/link unlinked services'); + +tie my %report_services_www, 'Tie::IxHash', + 'All virtual hosts' => [ $fsurl.'search/svc_www.cgi?svcnum', '' ], +; +$report_services_www{'Unlinked virtual hosts'} = [ $fsurl.'search/svc_www.cgi?UN_svcnum', 'Pre-Freeside virtual hosts without a customer record' ] + if $curuser->access_right('View/link unlinked services'); + +tie my %report_services_broadband, 'Tie::IxHash', + 'All broadband services' => [ $fsurl.'search/svc_broadband.cgi?svcnum', '' ], + #'Unlinked domain' => [ $fsurl.'search/svc_acct.cgi?UN_uid', 'Pre-Freeside domains without a customer record' ], +; + +tie my %report_services_phone, 'Tie::IxHash', + 'All phone numbers' => [ $fsurl.'search/svc_phone.cgi?svcnum', '' ], +; + +tie my %report_services_external, 'Tie::IxHash', + 'All external services' => [ $fsurl.'search/svc_external.cgi?id', '' ], +; +$report_services_external{'Unlinked external services'} = [ $fsurl.'search/svc_external.cgi?UN_id', 'Pre-Freeside domains without a customer record' ] + if $curuser->access_right('View/link unlinked services'); + +tie my %report_services, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration') ) { + $report_services{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi?orderby=active', 'Service definitions by number of active packages' ]; + $report_services{'separator'} = ''; +} +$report_services{'Accounts'} = [ \%report_services_acct, 'Access accounts and mailboxes' ]; +$report_services{'Domains'} = [ \%report_services_domain, 'Domains', ]; +$report_services{'Mail forwards'} = [ \%report_services_forward, 'Mail forwards', ]; +$report_services{'Virtual hosts'} = [ \%report_services_www, 'Virtual hosting', ]; +$report_services{'Broadband services'} = [ \%report_services_broadband, 'Fixed (username-less) broadband services', ]; +$report_services{'Phone numbers'} = [ \%report_services_phone, 'Telephone numbers', ]; +$report_services{'External services'} = [ \%report_services_external, 'External services', ]; + +tie my %report_packages, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration') ) { + $report_packages{'Package definitions'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ]; + $report_packages{'separator'} = ''; +} +$report_packages{'All customer packages'} = [ $fsurl.'search/cust_pkg.cgi?pkgnum', 'List all customer packages', ]; +$report_packages{'Suspended customer packages'} = [ $fsurl.'search/cust_pkg.cgi?magic=suspended', 'List suspended packages' ]; +$report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ]; +$report_packages{'Advanced package reports'} = [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ]; + +tie my %report_rating, 'Tie::IxHash', + 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ], + 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ], +; + +tie my %report_bill_event, 'Tie::IxHash', + 'All billing events' => [ $fsurl.'search/cust_bill_event.html', 'All billing events for a date range' ], + 'Invoice event errors' => [ $fsurl.'search/cust_bill_event.html?failed=1', 'failed credit cards, processor or printer problems, etc.' ], +; + +tie my %report_financial, 'Tie::IxHash', + 'Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time.html', 'Sales, credits and receipts summary graph' ], + 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ], + 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ], + 'Payment Report' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ], +; +$report_financial{'Payment Batch Report'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ] + if $conf->exists('batch-enable'); +$report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ]; +$report_financial{'Prepaid Income'} = [ $fsurl.'search/report_prepaid_income.html', 'Prepaid income (unearned revenue) report' ]; +$report_financial{'Sales Tax Liability'} = [ $fsurl.'search/report_tax.html', 'Sales tax liability report' ]; +; + +tie my %report_menu, 'Tie::IxHash'; +$report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ] + if $curuser->access_right('List customers'); +$report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ] + if $curuser->access_right('List invoices'); +$report_menu{'Packages'} = [ \%report_packages, 'Package reports' ] + if $curuser->access_right('List packages'); +$report_menu{'Services'} = [ \%report_services, 'Services reports' ] + if $curuser->access_right('List services'); +$report_menu{'Usage'} = [ \%report_rating, 'Usage reports' ] + if $curuser->access_right('List rating data'); +$report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ] + if $curuser->access_right('Billing event reports'); +$report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ] + if $curuser->access_right('Financial reports'); + +tie my %tools_importing, 'Tie::IxHash', + 'Import customers from CSV file' => [ $fsurl.'misc/cust_main-import.cgi', '' ], + 'Import one-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ], + 'Import Call Detail Records (CDRs) from CSV file' => [ $fsurl.'misc/cdr-import.html', '' ], +; + +tie my %tools_exporting, 'Tie::IxHash', + 'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ], +; + +# <!-- <BR>View active NAS ports: +# <A HREF="browse/nas.cgi">session server</A> --> +# <!-- or <A HREF="browse/nas-sqlradius.cgi">RADIUS</A> +# <BR> --> + +tie my %tools_menu, 'Tie::IxHash', (); +$tools_menu{'Quick payment entry'} = [ $fsurl.'misc/batch-cust_pay.html', 'Enter multiple payments in a batch' ] + if $curuser->access_right('Post payment batch'); +$tools_menu{'Process payment batches'} = [ $fsurl.'search/pay_batch.cgi?magic=_date;open=1;intransit=1', 'Process credit card and electronic check batches' ] + if $conf->exists('batch-enable') && $curuser->access_right('Process batches'); +$tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queue' ] + if $curuser->access_right('Job queue'); +$tools_menu{'Importing'} = [ \%tools_importing, 'Import tools' ] + if $curuser->access_right('Import'); +$tools_menu{'Exporting'} = [ \%tools_exporting, 'Export tools' ] + if $curuser->access_right('Export'); + +tie my %config_employees, 'Tie::IxHash', + 'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ], + 'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ], +; + +tie my %config_export_svc_pkg, 'Tie::IxHash', + 'View/Edit exports' => [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ], + 'View/Edit service definitions' => [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ], + 'View/Edit package definitions' => [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ], + 'View/Edit package classes' => [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ], +; + +tie my %config_agent, 'Tie::IxHash', + 'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ], + 'View/Edit agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ], +; + +tie my %config_billing, 'Tie::IxHash', + 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ], + 'View/Edit invoice events' => [ $fsurl.'browse/part_bill_event.cgi', 'Actions for overdue invoices' ], + 'View/Edit prepaid cards' => [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ], + 'View/Edit call rates and regions' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans, regions and prefixes for VoIP and call billing' ], + 'View/Edit locales and tax rates' => [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ], +; + +tie my %config_dialup, 'Tie::IxHash', + 'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ], +; + +tie my %config_broadband, 'Tie::IxHash', + 'View/Edit routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ], + 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ], +; + +tie my %config_misc, 'Tie::IxHash'; +$config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service. Tracked for informational purposes' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); +if ( $curuser->access_right('Configuration') ) { + $config_misc{'View/Edit virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ]; + $config_misc{'View/Edit message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ]; + $config_misc{'View/Edit inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]; +} + +tie my %config_menu, 'Tie::IxHash'; +if ( $curuser->access_right('Configuration' ) ) { + %config_menu = ( + 'Settings' => [ $fsurl.'config/config-view.cgi', '' ], + 'separator' => '', #its a separator! + 'Employees' => [ \%config_employees, '' ], + 'Provisioning, services and packages' + => [ \%config_export_svc_pkg, '' ], + 'Resellers' => [ \%config_agent, '' ], + 'Billing' => [ \%config_billing, '' ], + 'Dialup' => [ \%config_dialup, '' ], + 'Fixed (username-less) broadband' + => [ \%config_broadband, '' ], + ); +} +$config_menu{'Miscellaneous'} = [ \%config_misc, '' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); + +tie my %menu, 'Tie::IxHash', + 'Billing Main' => [ $fsurl, 'Billing start page', ], +; +if ( $conf->config('ticket_system') ) { + $menu{'Ticketing Main'} = + [ + ( $conf->config('ticket_system') eq 'RT_External' + ? FS::TicketSystem->baseurl() + : $fsurl.'rt/' + ), + 'Ticketing start page', + ], +} +$menu{'Reports'} = [ \%report_menu, 'Lists, reporting and graphing' ] + if keys %report_menu; +$menu{'Tools'} = [ \%tools_menu, 'Tools' ] + if keys %tools_menu; +$menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); + +use vars qw($gmenunum); +$gmenunum = 0; + +sub submenu { + my($submenu, $title) = @_; + my $menunum = $gmenunum++; + + #return two args: html, menuname + + "var myMenu$menunum = new WebFXMenu;\n". + #"myMenu$menunum.useAutoPosition = true;\n". + "myMenu$menunum.emptyText = '$title';\n". + + ( + join("\n", map { + + if ( !ref( $submenu->{$_} ) ) { + + "myMenu$menunum.add(new WebFXMenuSeparator());"; + + } else { + + my($url_or_submenu, $tooltip ) = @{ $submenu->{$_} }; + if ( ref($url_or_submenu) ) { + + my($subhtml, $submenuname ) = submenu($url_or_submenu, $_); #mmm, recursion + + "$subhtml\n". + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", null, \"$tooltip\", $submenuname ));"; + + } else { + + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", \"$url_or_submenu\", \"$tooltip\" ));"; + + } + + } + + } keys %$submenu ) + ). "\n". + "myMenu$menunum.width = 224\n", + + "myMenu$menunum"; + +} + +</%init> + diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html new file mode 100644 index 000000000..ec6c13fea --- /dev/null +++ b/httemplate/elements/menubar.html @@ -0,0 +1,10 @@ +% +% my($item, $url, @html); +% while (@_) { +% ($item, $url) = splice(@_,0,2); +% next if $item =~ /^\s*Main\s+Menu\s*$/i; +% push @html, qq!<A HREF="$url">$item</A>!; +% } +% + +<% join(' | ', @html) %> diff --git a/httemplate/elements/overlibmws.js b/httemplate/elements/overlibmws.js new file mode 100644 index 000000000..5ef50d68f --- /dev/null +++ b/httemplate/elements/overlibmws.js @@ -0,0 +1,697 @@ +/*
+ Do not remove or change this notice.
+ overlibmws.js core module - Copyright Foteos Macrides 2002-2005. All rights reserved.
+ Initial: August 18, 2002 - Last Revised: May 30, 2006
+ This module is subject to the same terms of usage as for Erik Bosrup's overLIB,
+ though only a minority of the code and API now correspond with Erik's version.
+ See the overlibmws Change History and Command Reference via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+ Give credit on sites that use overlibmws and submit changes so others can use them as well.
+ You can get Erik's version via: http://www.bosrup.com/web/overlib/
+*/
+
+// PRE-INIT -- Ignore these lines, configuration is below.
+var OLloaded=0,pmCnt=1,pMtr=new Array(),OLcmdLine=new Array(),OLrunTime=new Array(),OLv,OLudf,
+OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%"),OLrefXY,
+OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0,
+OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0,OLprintPI=0;
+if(typeof OLgateOK=='undefined')var OLgateOK=1;
+var OLp1or2c='inarray,caparray,caption,closetext,right,left,center,autostatuscap,padx,pady,'
++'below,above,vcenter,donothing',OLp1or2co='nofollow,background,offsetx,offsety,fgcolor,'
++'bgcolor,cgcolor,textcolor,capcolor,width,wrap,wrapmax,height,border,base,status,autostatus,'
++'snapx,snapy,fixx,fixy,relx,rely,midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,'
++'cgbackground,fullhtml,capicon,textfont,captionfont,textsize,captionsize,timeout,delay,hauto,'
++'vauto,nojustx,nojusty,fgclass,bgclass,cgclass,capbelow,textpadding,textfontclass,'
++'captionpadding,captionfontclass,sticky,noclose,mouseoff,offdelay,closecolor,closefont,'
++'closesize,closeclick,closetitle,closefontclass,decode',OLp1or2o='text,cap,close,hpos,vpos,'
++'padxl,padxr,padyt,padyb',OLp1co='label',OLp1or2=OLp1or2co+','+OLp1or2o,OLp1=OLp1co+','+'frame';
+OLregCmds(OLp1or2c+','+OLp1or2co+','+OLp1co);
+function OLud(v){return eval('typeof ol_'+v+'=="undefined"')?1:0;}
+
+// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions
+if(OLud('fgcolor'))var ol_fgcolor="#ccccff";
+if(OLud('bgcolor'))var ol_bgcolor="#333399";
+if(OLud('cgcolor'))var ol_cgcolor="#333399";
+if(OLud('textcolor'))var ol_textcolor="#000000";
+if(OLud('capcolor'))var ol_capcolor="#ffffff";
+if(OLud('closecolor'))var ol_closecolor="#eeeeff";
+if(OLud('textfont'))var ol_textfont="Verdana,Arial,Helvetica";
+if(OLud('captionfont'))var ol_captionfont="Verdana,Arial,Helvetica";
+if(OLud('closefont'))var ol_closefont="Verdana,Arial,Helvetica";
+if(OLud('textsize'))var ol_textsize=1;
+if(OLud('captionsize'))var ol_captionsize=1;
+if(OLud('closesize'))var ol_closesize=1;
+if(OLud('fgclass'))var ol_fgclass="";
+if(OLud('bgclass'))var ol_bgclass="";
+if(OLud('cgclass'))var ol_cgclass="";
+if(OLud('textpadding'))var ol_textpadding=2;
+if(OLud('textfontclass'))var ol_textfontclass="";
+if(OLud('captionpadding'))var ol_captionpadding=2;
+if(OLud('captionfontclass'))var ol_captionfontclass="";
+if(OLud('closefontclass'))var ol_closefontclass="";
+if(OLud('close'))var ol_close="Close";
+if(OLud('closeclick'))var ol_closeclick=0;
+if(OLud('closetitle'))var ol_closetitle="Click to Close";
+if(OLud('text'))var ol_text="Default Text";
+if(OLud('cap'))var ol_cap="";
+if(OLud('capbelow'))var ol_capbelow=0;
+if(OLud('background'))var ol_background="";
+if(OLud('width'))var ol_width=200;
+if(OLud('wrap'))var ol_wrap=0;
+if(OLud('wrapmax'))var ol_wrapmax=0;
+if(OLud('height'))var ol_height= -1;
+if(OLud('border'))var ol_border=1;
+if(OLud('base'))var ol_base=0;
+if(OLud('offsetx'))var ol_offsetx=10;
+if(OLud('offsety'))var ol_offsety=10;
+if(OLud('sticky'))var ol_sticky=0;
+if(OLud('nofollow'))var ol_nofollow=0;
+if(OLud('noclose'))var ol_noclose=0;
+if(OLud('mouseoff'))var ol_mouseoff=0;
+if(OLud('offdelay'))var ol_offdelay=300;
+if(OLud('hpos'))var ol_hpos=RIGHT;
+if(OLud('vpos'))var ol_vpos=BELOW;
+if(OLud('status'))var ol_status="";
+if(OLud('autostatus'))var ol_autostatus=0;
+if(OLud('snapx'))var ol_snapx=0;
+if(OLud('snapy'))var ol_snapy=0;
+if(OLud('fixx'))var ol_fixx= -1;
+if(OLud('fixy'))var ol_fixy= -1;
+if(OLud('relx'))var ol_relx=null;
+if(OLud('rely'))var ol_rely=null;
+if(OLud('midx'))var ol_midx=null;
+if(OLud('midy'))var ol_midy=null;
+if(OLud('ref'))var ol_ref="";
+if(OLud('refc'))var ol_refc='UL';
+if(OLud('refp'))var ol_refp='UL';
+if(OLud('refx'))var ol_refx=0;
+if(OLud('refy'))var ol_refy=0;
+if(OLud('fgbackground'))var ol_fgbackground="";
+if(OLud('bgbackground'))var ol_bgbackground="";
+if(OLud('cgbackground'))var ol_cgbackground="";
+if(OLud('padxl'))var ol_padxl=1;
+if(OLud('padxr'))var ol_padxr=1;
+if(OLud('padyt'))var ol_padyt=1;
+if(OLud('padyb'))var ol_padyb=1;
+if(OLud('fullhtml'))var ol_fullhtml=0;
+if(OLud('capicon'))var ol_capicon="";
+if(OLud('frame'))var ol_frame=self;
+if(OLud('timeout'))var ol_timeout=0;
+if(OLud('delay'))var ol_delay=0;
+if(OLud('hauto'))var ol_hauto=0;
+if(OLud('vauto'))var ol_vauto=0;
+if(OLud('nojustx'))var ol_nojustx=0;
+if(OLud('nojusty'))var ol_nojusty=0;
+if(OLud('label'))var ol_label="";
+if(OLud('decode'))var ol_decode=0;
+// ARRAY CONFIGURATION - See overlibConfig.txt for descriptions.
+if(OLud('texts'))var ol_texts=new Array("Text 0","Text 1");
+if(OLud('caps'))var ol_caps=new Array("Caption 0","Caption 1");
+// END CONFIGURATION -- Don't change anything below, all configuration is above.
+
+// INIT -- Runtime variables.
+var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0,
+o3_offdelay=300,o3_hpos=RIGHT,o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="",
+o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",o3_width=200,o3_wrap=0,
+o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0,
+o3_snapy=0,o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null,o3_ref="",
+o3_refc='UL',o3_refp='UL',o3_refx=0,o3_refy=0,o3_fgbackground="",o3_bgbackground="",
+o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0,o3_fullhtml=0,o3_vpos=BELOW,
+o3_capicon="",o3_textfont="Verdana,Arial,Helvetica",o3_captionfont="",o3_closefont="",
+o3_textsize=1,o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0,o3_hauto=0,
+o3_vauto=0,o3_nojustx=0,o3_nojusty=0,o3_close="",o3_closeclick=0,o3_closetitle="",o3_fgclass="",
+o3_bgclass="",o3_cgclass="",o3_textpadding=2,o3_textfontclass="",o3_captionpadding=2,
+o3_captionfontclass="",o3_closefontclass="",o3_capbelow=0,o3_label="",o3_decode=0,
+CSSOFF=DONOTHING,CSSCLASS=DONOTHING,OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0,over=null,
+OLfnRef="",OLhover=0,OLx=0,OLy=0,OLshowingsticky=0,OLallowmove=0,OLcC=null,
+OLua=navigator.userAgent.toLowerCase(),
+OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4),
+OLns6=(document.getElementById)?1:0,
+OLie4=(document.all)?1:0,
+OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0,
+OLmac=(OLua.indexOf('mac')>=0)?1:0,
+OLsaf=(OLua.indexOf('safari')>=0)?1:0,
+OLkon=(OLua.indexOf('konqueror')>=0)?1:0,
+OLkht=(OLsaf||OLkon)?1:0,
+OLopr=(OLua.indexOf('opera')>=0)?1:0,
+OLop7=(OLopr&&document.createTextNode)?1:0;
+if(OLopr){OLns4=OLns6=0;if(!OLop7)OLie4=0;}
+var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0,
+OLie5=0,OLie55=0;if(OLie4&&!OLop7){
+if((OLv=OLua.match(/msie (\d\.\d+)\.*/i))&&(OLv=parseFloat(OLv[1]))>=5.0){
+OLie5=1;OLns6=0;if(OLv>=5.5)OLie55=1;}if(OLns6)OLie4=0;}
+if(OLns4)window.onresize=function(){location.reload();}
+var OLchkMh=1,OLdw;
+if(OLns4||OLie4||OLns6)OLmh();
+else{overlib=nd=cClick=OLpageDefaults=no_overlib;}
+
+/*
+ PUBLIC FUNCTIONS
+*/
+// Loads defaults then args into runtime variables.
+function overlib(){
+if(!(OLloaded&&OLgateOK))return;
+if((OLexclusivePI)&&OLisExclusive(arguments))return true;
+if(OLchkMh)OLmh();
+if(OLndt&&!OLtimerid)OLndt=0;if(over)cClick();
+OLload(OLp1or2);OLload(OLp1);
+OLfnRef="";OLhover=0;
+OLsetRunTimeVar();
+OLparseTokens('o3_',arguments);
+if(!(over=OLmkLyr()))return false;
+if(o3_decode)OLdecode();
+if(OLprintPI)OLchkPrint();
+if(OLbubblePI)OLchkForBubbleEffect();
+if(OLdebugPI)OLsetDebugCanShow();
+if(OLshadowPI)OLinitShadow();
+if(OLiframePI)OLinitIfs();
+if(OLfilterPI)OLinitFilterLyr();
+if(OLexclusivePI&&o3_exclusive&&o3_exclusivestatus!="")o3_status=o3_exclusivestatus;
+else if(o3_autostatus==2&&o3_cap!="")o3_status=o3_cap;
+else if(o3_autostatus==1&&o3_text!="")o3_status=o3_text;
+if(!o3_delay){return OLmain();
+}else{OLdelayid=setTimeout("OLmain()",o3_delay);
+if(o3_status!=""){self.status=o3_status;return true;}
+else if(!(OLop7&&event&&event.type=='mouseover'))return false;}
+}
+
+// Clears popups if appropriate
+function nd(time){
+if(OLloaded&&OLgateOK){if(!((OLexclusivePI)&&OLisExclusive())){
+if(time&&over&&!o3_delay){if(OLtimerid>0)clearTimeout(OLtimerid);
+OLtimerid=(OLhover&&o3_frame==self&&!OLcursorOff())?0:
+setTimeout("cClick()",(o3_timeout=OLndt=time));}else{
+if(!OLshowingsticky){OLallowmove=0;if(over)OLhideObject(over);}}}}
+return false;
+}
+
+// Close function for stickies
+function cClick(){
+if(OLloaded&&OLgateOK){OLhover=0;if(over){
+if(OLovertwoPI&&over==over2)cClick2();OLhideObject(over);OLshowingsticky=0;}}
+return false;
+}
+
+// Sets page-specific defaults.
+function OLpageDefaults(){
+OLparseTokens('ol_',arguments);
+}
+
+// For unsupported browsers.
+function no_overlib(){return false;}
+
+/*
+ OVERLIB MAIN FUNCTION SET
+*/
+function OLmain(){
+o3_delay=0;
+if(o3_frame==self){if(o3_noclose)OLoptMOUSEOFF(0);else if(o3_mouseoff)OLoptMOUSEOFF(1);}
+if(o3_sticky)OLshowingsticky=1;OLdoLyr();OLallowmove=0;if(o3_timeout>0){
+if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_timeout);}
+if(o3_ref){OLrefXY=OLgetRefXY(o3_ref);if(OLrefXY[0]==null){o3_ref="";o3_midx=0;o3_midy=0;}}
+OLdisp(o3_status);if(OLdraggablePI)OLcheckDrag();
+if(o3_status!="")return true;else if(!(OLop7&&event&&event.type=='mouseover'))return false;
+}
+
+// Loads o3_ variables
+function OLload(c){var i,m=c.split(',');for(i=0;i<m.length;i++)eval('o3_'+m[i]+'=ol_'+m[i]);}
+
+// Chooses LGF
+function OLdoLGF(){
+return (o3_background!=''||o3_fullhtml)?OLcontentBackground(o3_text,o3_background,o3_fullhtml):
+(o3_cap=="")?OLcontentSimple(o3_text):
+(o3_sticky)?OLcontentCaption(o3_text,o3_cap,o3_close):OLcontentCaption(o3_text,o3_cap,'');
+}
+
+// Makes Layer
+function OLmkLyr(id,f,z){
+id=(id||'overDiv');f=(f||o3_frame);z=(z||1000);var fd=f.document,d=OLgetRefById(id,fd);
+if(!d){if(OLns4)d=fd.layers[id]=new Layer(1024,f);else if(OLie4&&!document.getElementById){
+fd.body.insertAdjacentHTML('BeforeEnd','<div id="'+id+'"></div>');d=fd.all[id];
+}else{d=fd.createElement('div');if(d){d.id=id;fd.body.appendChild(d);}}if(!d)return null;
+if(OLns4)d.zIndex=z;else{var o=d.style;o.position='absolute';o.visibility='hidden';o.zIndex=z;}}
+return d;
+}
+
+// Creates and writes layer content
+function OLdoLyr(){
+if(o3_background==''&&!o3_fullhtml){
+if(o3_fgbackground!='')o3_fgbackground=' background="'+o3_fgbackground+'"';
+if(o3_bgbackground!='')o3_bgbackground=' background="'+o3_bgbackground+'"';
+if(o3_cgbackground!='')o3_cgbackground=' background="'+o3_cgbackground+'"';
+if(o3_fgcolor!='')o3_fgcolor=' bgcolor="'+o3_fgcolor+'"';
+if(o3_bgcolor!='')o3_bgcolor=' bgcolor="'+o3_bgcolor+'"';
+if(o3_cgcolor!='')o3_cgcolor=' bgcolor="'+o3_cgcolor+'"';
+if(o3_height>0)o3_height=' height="'+o3_height+'"';else o3_height='';}
+if(!OLns4)OLrepositionTo(over,(OLns6?20:0),0);var lyrHtml=OLdoLGF();
+if(o3_sticky&&OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}
+if(o3_wrap&&!o3_fullhtml){OLlayerWrite(lyrHtml);
+o3_width=(OLns4?over.clip.width:over.offsetWidth);
+if(OLns4&&o3_wrapmax<1)o3_wrapmax=o3_frame.innerWidth-40;
+o3_wrap=0;if(o3_wrapmax>0&&o3_width>o3_wrapmax)o3_width=o3_wrapmax;lyrHtml=OLdoLGF();}
+OLlayerWrite(lyrHtml);o3_width=(OLns4?over.clip.width:over.offsetWidth);
+if(OLbubblePI)OLgenerateBubble(lyrHtml);
+}
+
+/*
+ LAYER GENERATION FUNCTIONS
+*/
+// Makes simple table without caption
+function OLcontentSimple(txt){
+var t=OLbgLGF()+OLfgLGF(txt)+OLbaseLGF();
+OLsetBackground('');return t;
+}
+
+// Makes table with caption and optional close link
+function OLcontentCaption(txt,title,close){
+var closing=(OLprintPI?OLprintCapLGF():''),closeevent='onmouseover',caption,t,
+cC='javascript:return '+OLfnRef+(OLovertwoPI&&over==over2?'cClick2();':'cClick();');
+if(o3_closeclick)closeevent=(o3_closetitle?'title="'+o3_closetitle+'" ':'')+'onclick';
+if(o3_capicon!='')o3_capicon='<img src="'+o3_capicon+'" /> ';
+if(close){closing+='<td align="right"><a href="'+cC+'" '
++closeevent+'="'+cC+'"'+(o3_closefontclass?' class="'+o3_closefontclass
++'">':'>'+OLlgfUtil(0,0,'','span',o3_closecolor,o3_closefont,o3_closesize))+close
++(o3_closefontclass?'':OLlgfUtil(1,0,'','span'))+'</a></td>';}
+caption='<table'+OLwd(0)+' border="0" cellpadding="'+o3_captionpadding+'" cellspacing="0"'
++(o3_cgclass?' class="'+o3_cgclass+'"':o3_cgcolor+o3_cgbackground)+'><tr><td'+OLwd(0)
++(o3_cgclass?' class="'+o3_cgclass+'">':'>')+(o3_captionfontclass?'<div class="'
++o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',o3_capcolor,o3_captionfont,
+o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+'</td>'+closing+'</tr></table>';
+t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF();
+OLsetBackground('');return t;
+}
+
+// For BACKGROUND and FULLHTML commands
+function OLcontentBackground(txt, image, hasfullhtml){
+var t;if(hasfullhtml){t=txt;}else{t='<table'+OLwd(1)
++' border="0" cellpadding="0" cellspacing="0" '+'height="'+o3_height
++'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'
++o3_padxl+'"></td><td valign="top"'+OLwd(2)+'>'
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+
+OLlgfUtil(1,0,'','div')+'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'
++o3_padyb+'"></td></tr></table>';}
+OLsetBackground(image);return t;
+}
+
+// LGF utilities
+function OLbgLGF(){
+return '<table'+OLwd(1)+o3_height+' border="0" cellpadding="'+o3_border+'" cellspacing="0"'
++(o3_bgclass?' class="'+o3_bgclass+'"':o3_bgcolor+o3_bgbackground)+'><tr><td>';
+}
+function OLfgLGF(t){
+return '<table'+OLwd(0)+o3_height+' border="0" cellpadding="'+o3_textpadding
++'" cellspacing="0"'+(o3_fgclass?' class="'+o3_fgclass+'"':o3_fgcolor+o3_fgbackground)
++'><tr><td valign="top"'+(o3_fgclass?' class="'+o3_fgclass+'"':'')+'>'
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t
++(OLprintPI?OLprintFgLGF():'')+OLlgfUtil(1,0,'','div')+'</td></tr></table>';
+}
+function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){
+if(end)return ('</'+(OLns4?'font'+(stg?'></strong':''):ele)+'>');else return (tfc?'<div '
++'class="' +tfc +'">':('<'+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="'
++OLquoteMultiNameFonts(fac)+'" size="'+siz:ele+' style="color:'+col
++(stg?';font-weight:bold':'')+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:'
++siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">'));
+}
+function OLquoteMultiNameFonts(f){
+var i,v,pM=f.split(',');
+for(i=0;i<pM.length;i++){v=pM[i];v=v.replace(/^\s+/,'').replace(/\s+$/,'');
+if(/\s/.test(v) && !/['"]/.test(v)){v="\'"+v+"\'";pM[i]=v;}}
+return pM.join();
+}
+function OLbaseLGF(){
+return ((o3_base>0&&!o3_wrap)?('<table width="100%" border="0" cellpadding="0" cellspacing="0"'
++(o3_bgclass?' class="'+o3_bgclass+'"':'')+'><tr><td height="'+o3_base
++'"></td></tr></table>'):'')+'</td></tr></table>';
+}
+function OLwd(a){
+return(o3_wrap?'':' width="'+(!a?'100%':(a==1?o3_width:(o3_width-o3_padxl-o3_padxr)))+'"');
+}
+
+// Loads image into the div.
+function OLsetBackground(i){
+if(i==''){if(OLns4)over.background.src=null;
+else{if(OLns6)over.style.width='';over.style.backgroundImage='none';}
+}else{if(OLns4)over.background.src=i;
+else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i+')';}}
+}
+
+/*
+ HANDLING FUNCTIONS
+*/
+// Displays layer
+function OLdisp(s){
+if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer();
+if(OLndt)OLshowObject(over);else OLshowid=setTimeout("OLshowObject(over)",1);
+OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s;
+}
+
+// Decides placement of layer.
+function OLplaceLayer(){
+var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0,
+o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;
+if(!OLkht&&o&&o.clientWidth)iWd=o.clientWidth;
+else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth));
+if(SB>20)SB=20;iWd=o3_frame.innerWidth;}
+pgLeft=(OLie4)?o.scrollLeft:o3_frame.pageXOffset;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0);
+LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);}
+if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){
+if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='UR'||o3_refp=='LR')X-=5;}
+else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){
+if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else
+if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;}
+}else{if(o3_midx!=null){
+X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx);
+}else{if(o3_relx!=null){
+if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB;
+}else{X=o3_fixx+LM;}}}
+}else{
+if(o3_hauto){
+if(o3_hpos==LEFT&&OLx-pgLeft<iWd/2&&OLx-pWd-o3_offsetx<pgLeft+LM)o3_hpos=RIGHT;else
+if(o3_hpos==RIGHT&&OLx-pgLeft>iWd/2&&OLx+pWd+o3_offsetx>pgLeft+iWd-SB)o3_hpos=LEFT;}
+X=(o3_hpos==CENTER)?parseInt(OLx-((pWd+CX)/2)+o3_offsetx):
+(o3_hpos==LEFT)?OLx-o3_offsetx-pWd:OLx+o3_offsetx;
+if(o3_snapx>1){
+snp=X % o3_snapx;
+if(o3_hpos==LEFT){X=X-(o3_snapx+snp);}else{X=X+(o3_snapx-snp);}}}
+if(!o3_nojustx&&X+pWd>pgLeft+iWd-SB)
+X=iWd+pgLeft-pWd-SB;if(!o3_nojustx&&X-LM<pgLeft)X=pgLeft+LM;
+pgTop=OLie4?o.scrollTop:o3_frame.pageYOffset;
+if(!OLkht&&!nsb&&o&&o.clientHeight)iHt=o.clientHeight;
+else if(o3_frame.innerHeight)iHt=o3_frame.innerHeight;
+if(OLbubblePI&&o3_bubble)pHt=OLbubbleHt;else pHt=OLns4?over.clip.height:over.offsetHeight;
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){TM=(o3_shadowy<0)?Math.abs(o3_shadowy):0;
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)BM=CY=5;else
+BM=(o3_shadowy>0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);}
+if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){
+if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){
+if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){
+if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else
+if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;}
+}else{if(o3_midy!=null){
+Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy);
+}else{if(o3_rely!=null){
+if(o3_rely>=0)Y=pgTop+o3_rely+TM;else Y=pgTop+o3_rely+iHt-pHt-BM;}else{
+Y=o3_fixy+TM;}}}
+}else{
+if(o3_vauto){
+if(o3_vpos==ABOVE&&OLy-pgTop<iHt/2&&OLy-pHt-o3_offsety<pgTop)o3_vpos=BELOW;else
+if(o3_vpos==BELOW&&OLy-pgTop>iHt/2&&OLy+pHt+o3_offsety+((OLns4||OLkht)?17:0)>pgTop+iHt-BM)
+o3_vpos=ABOVE;}Y=(o3_vpos==VCENTER)?parseInt(OLy-((pHt+CY)/2)+o3_offsety):
+(o3_vpos==ABOVE)?OLy-(pHt+o3_offsety+BM):OLy+o3_offsety+TM;
+if(o3_snapy>1){
+snp=Y % o3_snapy;
+if(pHt>0&&o3_vpos==ABOVE){Y=Y-(o3_snapy+snp);}else{Y=Y+(o3_snapy-snp);}}}
+if(!o3_nojusty&&Y+pHt+BM>pgTop+iHt)Y=pgTop+iHt-pHt-BM;if(!o3_nojusty&&Y-TM<pgTop)Y=pgTop+TM;
+OLrepositionTo(over,X,Y);
+if(OLshadowPI)OLrepositionShadow(X,Y);if(OLiframePI)OLrepositionIfs(X,Y);
+if(OLns6&&o3_frame.innerHeight){iHt=o3_frame.innerHeight;OLrepositionTo(over,X,Y);}
+if(OLscrollPI)OLchkScroll(X-pgLeft,Y-pgTop);
+}
+
+// Chooses body or documentElement
+function OLfd(f){
+var fd=((f)?f:o3_frame).document,fdc=fd.compatMode,fdd=fd.documentElement;
+return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement:fd.body;
+}
+
+// Gets location of REFerence object
+function OLgetRefXY(r,d){
+var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of;
+if(!o)return [null,null];
+if(OLns4){if(typeof o.length!='undefined'&&o.length>1){
+ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY;
+}else{if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){
+rXY[0]+=o.x;rXY[1]+=o.y;}else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}}
+}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');}
+of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1];
+return rXY;
+}
+function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);}
+
+// Seeks REFerence by id
+function OLgetRefById(l,d){
+l=(l||'overDiv');d=(d||o3_frame.document);var j,r;
+if(OLie4&&d.all)return d.all[l];if(d.getElementById)return d.getElementById(l);
+if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];
+for(j=0;j<d.layers.length;j++){r=OLgetRefById(l,d.layers[j].document);if(r)return r;}}
+return null;
+}
+
+// Seeks REFerence by name
+function OLgetRefByName(l,d){
+d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'):
+OLns6?d.getElementsByTagName('iframe'):null;
+if(typeof d.images!='undefined'&&d.images[l])return d.images[l];
+if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l];
+if(v)for(j=0;j<v.length;j++)if(v[j].name==l)return v[j];
+if(d.layers&&d.layers.length>0)for(j=0;j<d.layers.length;j++){
+r=OLgetRefByName(l,d.layers[j].document);
+if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}
+return null;
+}
+
+// Gets layer vs REFerence offsets
+function OLgetRefOffsets(o){
+var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0];
+pW=(OLbubblePI&&o3_bubble)?o3_width:OLns4?over.clip.width:over.offsetWidth;
+pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?over.clip.height:over.offsetHeight;
+if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height;
+}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{
+W=(OLns4)?o.clip.width:o.offsetWidth;H=(OLns4)?o.clip.height:o.offsetHeight;}
+if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);H+=2*parseInt(o.border);}
+if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0];
+}else if(c=='UR'){of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0];
+}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:(p=='LR')?[-pW,H-pH]:[0,H];
+}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]:
+[W,H];}
+return of;
+}
+
+// Gets x or y location of object
+function OLpageLoc(o,t){
+var l=0;while(o.offsetParent&&o.offsetParent.tagName.toLowerCase()!='html'){
+l+=o['offset'+t];o=o.offsetParent;}l+=o['offset'+t];
+return l;
+}
+
+// Moves layer
+function OLmouseMove(e){
+var e=(e||event);
+OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick);
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById()
+||(OLovertwoPI&&over2==over&&over==OLgetRefById('overDiv2')))){
+OLplaceLayer();if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}
+if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)OLcC();else
+{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("OLcC()",o3_offdelay);}
+}
+
+// Capture mouse and chain other scripts.
+function OLmh(){
+var fN,f,j,k,s,mh=OLmouseMove,w=(OLns4&&window.onmousemove),re=/function[ ]*(\w*)\(/;
+OLdw=document;if(document.onmousemove||w){if(w)OLdw=window;f=OLdw.onmousemove.toString();
+fN=f.match(re);if(!fN||fN[1]=='anonymous'||fN[1]=='OLmouseMove'){OLchkMh=0;return;}
+if(fN[1])s=fN[1]+'(e)';else{j=f.indexOf('{');k=f.lastIndexOf('}')+1;s=f.substring(j,k);}
+s+=';OLmouseMove(e);';mh=new Function('e',s);}
+OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE);
+}
+
+/*
+ PARSING
+*/
+function OLparseTokens(pf,ar){
+var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);
+for(i=0;i< ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;}
+else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0;
+}else{
+if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[++i]+']');continue;}
+if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[++i]+']');continue;}
+if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;}
+if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;}
+if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;}
+if(ar[i]==BACKGROUND){q(ar[++i],pf+'background');continue;}
+if(Math.abs(ar[i])==NOCLOSE){t(ar[i],pf+'noclose');continue;}
+if(Math.abs(ar[i])==MOUSEOFF){t(ar[i],pf+'mouseoff');continue;}
+if(ar[i]==OFFDELAY){p(ar[++i],pf+'offdelay');continue;}
+if(ar[i]==RIGHT||ar[i]==LEFT||ar[i]==CENTER){p(ar[i],pf+'hpos');continue;}
+if(ar[i]==OFFSETX){p(ar[++i],pf+'offsetx');continue;}
+if(ar[i]==OFFSETY){p(ar[++i],pf+'offsety');continue;}
+if(ar[i]==FGCOLOR){q(ar[++i],pf+'fgcolor');continue;}
+if(ar[i]==BGCOLOR){q(ar[++i],pf+'bgcolor');continue;}
+if(ar[i]==CGCOLOR){q(ar[++i],pf+'cgcolor');continue;}
+if(ar[i]==TEXTCOLOR){q(ar[++i],pf+'textcolor');continue;}
+if(ar[i]==CAPCOLOR){q(ar[++i],pf+'capcolor');continue;}
+if(ar[i]==CLOSECOLOR){q(ar[++i],pf+'closecolor');continue;}
+if(ar[i]==WIDTH){p(ar[++i],pf+'width');continue;}
+if(Math.abs(ar[i])==WRAP){t(ar[i],pf+'wrap');continue;}
+if(ar[i]==WRAPMAX){p(ar[++i],pf+'wrapmax');continue;}
+if(ar[i]==HEIGHT){p(ar[++i],pf+'height');continue;}
+if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;}
+if(ar[i]==BASE){p(ar[++i],pf+'base');continue;}
+if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;}
+if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}
+if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus';
+eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}
+if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;}
+if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;}
+if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;}
+if(ar[i]==FIXX){p(ar[++i],pf+'fixx');continue;}
+if(ar[i]==FIXY){p(ar[++i],pf+'fixy');continue;}
+if(ar[i]==RELX){p(ar[++i],pf+'relx');continue;}
+if(ar[i]==RELY){p(ar[++i],pf+'rely');continue;}
+if(ar[i]==MIDX){p(ar[++i],pf+'midx');continue;}
+if(ar[i]==MIDY){p(ar[++i],pf+'midy');continue;}
+if(ar[i]==REF){q(ar[++i],pf+'ref');continue;}
+if(ar[i]==REFC){q(ar[++i],pf+'refc');continue;}
+if(ar[i]==REFP){q(ar[++i],pf+'refp');continue;}
+if(ar[i]==REFX){p(ar[++i],pf+'refx');continue;}
+if(ar[i]==REFY){p(ar[++i],pf+'refy');continue;}
+if(ar[i]==FGBACKGROUND){q(ar[++i],pf+'fgbackground');continue;}
+if(ar[i]==BGBACKGROUND){q(ar[++i],pf+'bgbackground');continue;}
+if(ar[i]==CGBACKGROUND){q(ar[++i],pf+'cgbackground');continue;}
+if(ar[i]==PADX){p(ar[++i],pf+'padxl');p(ar[++i],pf+'padxr');continue;}
+if(ar[i]==PADY){p(ar[++i],pf+'padyt');p(ar[++i],pf+'padyb');continue;}
+if(Math.abs(ar[i])==FULLHTML){t(ar[i],pf+'fullhtml');continue;}
+if(ar[i]==BELOW||ar[i]==ABOVE||ar[i]==VCENTER){p(ar[i],pf+'vpos');continue;}
+if(ar[i]==CAPICON){q(ar[++i],pf+'capicon');continue;}
+if(ar[i]==TEXTFONT){q(ar[++i],pf+'textfont');continue;}
+if(ar[i]==CAPTIONFONT){q(ar[++i],pf+'captionfont');continue;}
+if(ar[i]==CLOSEFONT){q(ar[++i],pf+'closefont');continue;}
+if(ar[i]==TEXTSIZE){q(ar[++i],pf+'textsize');continue;}
+if(ar[i]==CAPTIONSIZE){q(ar[++i],pf+'captionsize');continue;}
+if(ar[i]==CLOSESIZE){q(ar[++i],pf+'closesize');continue;}
+if(ar[i]==TIMEOUT){p(ar[++i],pf+'timeout');continue;}
+if(ar[i]==DELAY){p(ar[++i],pf+'delay');continue;}
+if(Math.abs(ar[i])==HAUTO){t(ar[i],pf+'hauto');continue;}
+if(Math.abs(ar[i])==VAUTO){t(ar[i],pf+'vauto');continue;}
+if(Math.abs(ar[i])==NOJUSTX){t(ar[i],pf+'nojustx');continue;}
+if(Math.abs(ar[i])==NOJUSTY){t(ar[i],pf+'nojusty');continue;}
+if(Math.abs(ar[i])==CLOSECLICK){t(ar[i],pf+'closeclick');continue;}
+if(ar[i]==CLOSETITLE){q(ar[++i],pf+'closetitle');continue;}
+if(ar[i]==FGCLASS){q(ar[++i],pf+'fgclass');continue;}
+if(ar[i]==BGCLASS){q(ar[++i],pf+'bgclass');continue;}
+if(ar[i]==CGCLASS){q(ar[++i],pf+'cgclass');continue;}
+if(ar[i]==TEXTPADDING){p(ar[++i],pf+'textpadding');continue;}
+if(ar[i]==TEXTFONTCLASS){q(ar[++i],pf+'textfontclass');continue;}
+if(ar[i]==CAPTIONPADDING){p(ar[++i],pf+'captionpadding');continue;}
+if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;}
+if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;}
+if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;}
+if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;}
+if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;}
+if(ar[i]==DONOTHING){continue;}
+i=OLparseCmdLine(pf,i,ar);}}
+if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function();
+if(pf=='o3_')OLfontSize();
+}
+function OLpar(a,v){eval(v+'='+a);}
+function OLparQuo(a,v){eval(v+"='"+OLescSglQt(a)+"'");}
+function OLescSglQt(s){return s.toString().replace(/'/g,"\\'");}
+function OLtoggle(a,v){eval(v+'=('+v+'==0&&'+a+'>=0)?1:0');}
+function OLhasDims(s){return /[%\-a-z]+$/.test(s);}
+function OLfontSize(){
+var i;if(OLhasDims(o3_textsize)){if(OLns4)o3_textsize="2";}else
+if(!OLns4){i=parseInt(o3_textsize);o3_textsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else
+if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else
+if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];}
+if(OLprintPI)OLprintDims();
+}
+function OLdecode(){
+var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826)
+&&typeof decodeURIComponent?decodeURIComponent:u;if(typeof(window.TypeError)=='function'){
+if(re.test(t)){eval(new Array('try{','o3_text=d(t);','}catch(e){','o3_text=u(t);',
+'}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){',
+'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);}
+}
+
+/*
+ LAYER FUNCTIONS
+*/
+// Writes to layer
+function OLlayerWrite(t){
+t+="\n";
+if(OLns4){over.document.write(t);over.document.close();
+}else if(typeof over.innerHTML!='undefined'){if(OLieM)over.innerHTML='';over.innerHTML=t;
+}else{range=o3_frame.document.createRange();range.setStartAfter(over);
+domfrag=range.createContextualFragment(t);
+while(over.hasChildNodes()){over.removeChild(over.lastChild);}
+over.appendChild(domfrag);}
+if(OLprintPI)over.print=o3_print?t:null;
+}
+
+// Makes object visible
+function OLshowObject(o){
+OLshowid=0;o=(OLns4)?o:o.style;
+if(((OLfilterPI)&&!OLchkFilter(o))||!OLfilterPI)o.visibility="visible";
+if(OLshadowPI)OLshowShadow();if(OLiframePI)OLshowIfs();if(OLhidePI)OLhideUtil(1,1,0);
+}
+
+// Hides object
+function OLhideObject(o){
+if(OLshowid>0){clearTimeout(OLshowid);OLshowid=0;}
+if(OLtimerid>0)clearTimeout(OLtimerid);if(OLdelayid>0)clearTimeout(OLdelayid);
+OLtimerid=0;OLdelayid=0;self.status="";o3_label=ol_label;
+if(o3_frame!=self)o=OLgetRefById();
+if(o){if(o.onmouseover)o.onmouseover=null;
+if(OLscrollPI&&o==over)OLclearScroll();
+if(OLdraggablePI)OLclearDrag();
+if(OLfilterPI)OLcleanupFilter(o);if(OLshadowPI)OLhideShadow();
+var os=(OLns4)?o:o.style;os.visibility="hidden";
+if(OLhidePI&&o==over)OLhideUtil(0,0,1);if(OLiframePI)OLhideIfs(o);}
+}
+
+// Moves layer
+function OLrepositionTo(o,xL,yL){
+o=(OLns4)?o:o.style;
+o.left=(OLns4?xL:xL+'px');
+o.top=(OLns4?yL:yL+'px');
+}
+
+// Handle NOCLOSE-MOUSEOFF
+function OLoptMOUSEOFF(c){
+if(!c)o3_close="";
+over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}}
+}
+function OLcursorOff(){
+var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight,
+left=parseInt(o.left),top=parseInt(o.top),
+right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);
+if(OLx<left||OLx>right||OLy<top||OLy>bottom)return true;
+return false;
+}
+
+/*
+ REGISTRATION
+*/
+function OLsetRunTimeVar(){
+if(OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();
+}
+function OLparseCmdLine(pf,i,ar){
+if(OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){
+var j=OLcmdLine[k](pf,i,ar);if(j>-1){i=j;break;}}}
+return i;
+}
+function OLregCmds(c){
+if(typeof c!='string')return;
+var pM=c.split(',');pMtr=pMtr.concat(pM);
+for(var i=0;i<pM.length;i++)eval(pM[i].toUpperCase()+'='+pmCnt++);
+}
+function OLregRunTimeFunc(f){
+if(typeof f=='object')OLrunTime=OLrunTime.concat(f);
+else OLrunTime[OLrunTime.length++]=f;
+}
+function OLregCmdLineFunc(f){
+if(typeof f=='object')OLcmdLine=OLcmdLine.concat(f);
+else OLcmdLine[OLcmdLine.length++]=f;
+}
+
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_draggable.js b/httemplate/elements/overlibmws_draggable.js new file mode 100644 index 000000000..0d25f842e --- /dev/null +++ b/httemplate/elements/overlibmws_draggable.js @@ -0,0 +1,78 @@ +/*
+ overlibmws_draggable.js plug-in module - Copyright Foteos Macrides 2002=2005
+ For support of the DRAGGABLE feature.
+ Initial: August 24, 2002 - Last Revised: March 2, 2006
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+OLregCmds('draggable');
+
+// DEFAULT CONFIGURATION
+if(OLud('draggable'))var ol_draggable=0;
+// END CONFIGURATION
+
+var o3_draggable=0,o3_dragging=0,OLmMv,OLcX,OLcY,OLcbX,OLcbY;
+function OLloadDraggable(){OLload('draggable');}
+function OLparseDraggable(pf,i,ar){
+var k=i;
+if(k<ar.length){if(Math.abs(ar[k])==DRAGGABLE){OLtoggle(ar[k],pf+'draggable');return k;}}
+return -1;
+}
+
+function OLcheckDrag(){
+if(o3_draggable){if(o3_sticky&&(o3_frame==self))initDrag();else o3_draggable=0;}
+}
+function initDrag(){
+OLmMv=OLdw.onmousemove;o3_dragging=0;
+if(OLns4){document.captureEvents(Event.MOUSEDOWN|Event.CLICK);
+document.onmousedown=OLgrabEl;;document.onclick=function(e){return routeEvent(e);}}
+else{over.onmousedown=OLgrabEl;OLsetDrgCur(1);}
+}
+function OLsetDrgCur(d){if(!OLns4)over.style.cursor=(d?'move':'auto');}
+
+function OLgrabEl(e){
+var e=(e||event);
+var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(e.altKey||(OLop7&&e.ctrlKey)));o3_dragging=1;
+if(cKy){OLsetDrgCur(0);document.onmouseup=function(){OLsetDrgCur(1);o3_dragging=0;}
+return(OLns4?routeEvent(e):true);}
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(OLie4)over.onselectstart=function(){return false;}
+if(OLns4){OLcX=OLx;OLcY=OLy;document.captureEvents(Event.MOUSEUP)}else{
+OLcX=OLx-(OLns4?over.left:parseInt(over.style.left));
+OLcY=OLy-(OLns4?over.top:parseInt(over.style.top));
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLcbX=OLx-(parseInt(bkdrop.style.left));
+OLcbY=OLy-(parseInt(bkdrop.style.top));}}OLdw.onmousemove=OLmoveEl;
+document.onmouseup=function(){
+if(OLie4)over.onselectstart=null;o3_dragging=0;OLdw.onmousemove=OLmMv;}
+return(OLns4?routeEvent(e):false);
+}
+
+function OLmoveEl(e){
+var e=(e||event);
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);
+if(o3_dragging){if(OLns4){over.moveBy(OLx-OLcX,OLy-OLcY);
+if(OLshadowPI&&bkdrop&&o3_shadow)bkdrop.moveBy(OLx-OLcX,OLy-OLcY);}
+else{OLrepositionTo(over,OLx-OLcX,OLy-OLcY);
+if((OLiframePI)&&OLie55&&OLifsP1)OLrepositionTo(OLifsP1,OLx-OLcX,OLy-OLcY);
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLrepositionTo(bkdrop,OLx-OLcbX,OLy-OLcbY);
+if((OLiframePI)&&OLie55&&OLifsSh)OLrepositionTo(OLifsSh,OLx-OLcbX,OLy-OLcbY);}}
+if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}if(OLns4){OLcX=OLx;OLcY=OLy;}
+return false;
+}
+
+function OLclearDrag(){
+if(OLns4){document.releaseEvents(Event.MOUSEDOWN|Event.MOUSEUP|Event.CLICK);
+document.onmousedown=document.onclick=null;}else{over.onmousedown=null;OLsetDrgCur(0);}
+document.onmouseup=null;o3_dragging=0;
+}
+
+OLregRunTimeFunc(OLloadDraggable);
+OLregCmdLineFunc(OLparseDraggable);
+
+OLdraggablePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/overlibmws_iframe.js b/httemplate/elements/overlibmws_iframe.js new file mode 100644 index 000000000..e3032f2ee --- /dev/null +++ b/httemplate/elements/overlibmws_iframe.js @@ -0,0 +1,93 @@ +/*
+ overlibmws_iframe.js plug-in module - Copyright Foteos Macrides 2003-2005
+ Masks system controls to prevent obscuring of popops for IE v5.5 or higher.
+ Initial: October 19, 2003 - Last Revised: May 15, 2005
+ See the Change History and Command Reference for overlibmws via:
+
+ http://www.macridesweb.com/oltest/
+
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html
+*/
+
+OLloaded=0;
+
+var OLifsP1=null,OLifsSh=null,OLifsP2=null;
+
+// IFRAME SHIM SUPPORT FUNCTIONS
+function OLinitIfs(){
+if(!OLie55)return;
+if((OLovertwoPI)&&over2&&over==over2){
+var o=o3_frame.document.all['overIframeOvertwo'];
+if(!o||OLifsP2!=o){OLifsP2=null;OLgetIfsP2Ref();}return;}
+o=o3_frame.document.all['overIframe'];
+if(!o||OLifsP1!=o){OLifsP1=null;OLgetIfsRef();}
+if((OLshadowPI)&&o3_shadow){o=o3_frame.document.all['overIframeShadow'];
+if(!o||OLifsSh!=o){OLifsSh=null;OLgetIfsShRef();}}
+}
+
+function OLsetIfsRef(o,i,z){
+o.id=i;o.src='javascript:false;';o.scrolling='no';var os=o.style;
+os.position='absolute';os.top=0;os.left=0;os.width=1;os.height=1;os.visibility='hidden';
+os.zIndex=over.style.zIndex-z;os.filter='Alpha(style=0,opacity=0)';
+}
+
+function OLgetIfsRef(){
+if(OLifsP1||!OLie55)return;
+OLifsP1=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP1,'overIframe',2);
+o3_frame.document.body.appendChild(OLifsP1);
+}
+
+function OLgetIfsShRef(){
+if(OLifsSh||!OLie55)return;
+OLifsSh=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsSh,'overIframeShadow',3);
+o3_frame.document.body.appendChild(OLifsSh);
+}
+
+function OLgetIfsP2Ref(){
+if(OLifsP2||!OLie55)return;
+OLifsP2=o3_frame.document.createElement('iframe');
+OLsetIfsRef(OLifsP2,'overIframeOvertwo',1);
+o3_frame.document.body.appendChild(OLifsP2);
+}
+
+function OLsetDispIfs(o,w,h){
+var os=o.style;
+os.width=w+'px';os.height=h+'px';os.clip='rect(0px '+w+'px '+h+'px 0px)';
+o.filters.alpha.enabled=true;
+}
+
+function OLdispIfs(){
+if(!OLie55)return;
+var wd=over.offsetWidth,ht=over.offsetHeight;
+if(OLfilterPI&&o3_filter&&o3_filtershadow){wd+=5;ht+=5;}
+if((OLovertwoPI)&&over2&&over==over2){
+if(!OLifsP2)return;
+OLsetDispIfs(OLifsP2,wd,ht);return;}
+if(!OLifsP1)return;
+OLsetDispIfs(OLifsP1,wd,ht);
+if((!OLshadowPI)||!o3_shadow||!OLifsSh)return;
+OLsetDispIfs(OLifsSh,wd,ht);
+}
+
+function OLshowIfs(){
+if(OLifsP1){OLifsP1.style.visibility="visible";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="visible";}
+}
+
+function OLhideIfs(o){
+if(!OLie55||o!=over)return;
+if(OLifsP1)OLifsP1.style.visibility="hidden";
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="hidden";
+}
+
+function OLrepositionIfs(X,Y){
+if(OLie55){if((OLovertwoPI)&&over2&&over==over2){
+if(OLifsP2)OLrepositionTo(OLifsP2,X,Y);}
+else{if(OLifsP1){OLrepositionTo(OLifsP1,X,Y);if((OLshadowPI)&&o3_shadow&&OLifsSh)
+OLrepositionTo(OLifsSh,X+o3_shadowx,Y+o3_shadowy);}}}
+}
+
+OLiframePI=1;
+OLloaded=1;
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html new file mode 100644 index 000000000..2327594a8 --- /dev/null +++ b/httemplate/elements/pager.html @@ -0,0 +1,43 @@ +% +% +% 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/phonenumber.html b/httemplate/elements/phonenumber.html new file mode 100644 index 000000000..0e730ebcc --- /dev/null +++ b/httemplate/elements/phonenumber.html @@ -0,0 +1,22 @@ +% +% my( $number, %opt ) = @_; +% my $conf = new FS::Conf; +% ( my $snumber = $number ) =~ s/\D//g; +% + +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/iframecontentmws.js"></SCRIPT> +% if ( length($number) ) { + + <% $number %> +% if ( $opt{'callable'} && $conf->config('vonage-username') ) { + + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('https://secure.click2callu.com/tpcc/makecall?username=<% $conf->config('vonage-username') %>&password=<% $conf->config('vonage-password') %>&fromnumber=<% $conf->config('vonage-fromnumber')%>&tonumber=1<% $snumber %>', 240, 64, 'call_popup'), CAPTION, 'Initiating call', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE, WIDTH, 240, HEIGHT, 64 ); return false;" TITLE="Call this number"><IMG SRC="<%$fsurl%>images/red_telephone_mimooh_01.png" BORDER=0 ALT="Call this number"></A> +% } +% } else { + + +% } + diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html new file mode 100644 index 000000000..3894feef9 --- /dev/null +++ b/httemplate/elements/progress-init.html @@ -0,0 +1,85 @@ +% +% my( $formname, $fields, $action, $url_or_message, $key ) = @_; +% $key = '' unless defined $key; +% +% my $url_or_message_link; +% if ( ref($url_or_message) ) { #its a message or something +% $url_or_message_link = +% 'message='. uri_escape( $url_or_message->{'message'} ) +% } else { +% $url_or_message_link = "url=$url_or_message"; +% } +% + + +<% include('/elements/xmlhttp.html', + 'method' => 'POST', + 'url' => $action, + 'subs' => [ 'start_job' ], + 'key' => $key, + ) +%> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript"> +function OLiframeContent(src, width, height, name) { + return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"' + +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">' + +'<div>[iframe not supported]</div></iframe>'); +} + +function <%$key%>process () { + + //alert('<%$key%>process for form <%$formname%>'); + + if ( document.<%$formname%>.submit.disabled == false ) { + document.<%$formname%>.submit.disabled=true; + } + + overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + + var Hash = new Array(); + var x = 0; + var fieldName; + for (var i = 0; i<document.<%$formname%>.elements.length; i++) { + field = document.<%$formname%>.elements[i]; + if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %> + ) + { + if ( field.type == 'select-multiple' ) { + //alert('select-multiple ' + field.name); + for (var j=0; j < field.options.length; j++) { + if ( field.options[j].selected ) { + //alert(field.name + ' => ' + field.options[j].value); + Hash[x++] = field.name; + Hash[x++] = field.options[j].value; + } + } + } else if ( ( field.type != 'radio' && field.type != 'checkbox' ) + || ( ( field.type == 'radio' || field.type == 'checkbox' ) + && document.<%$formname%>.elements[i].checked + ) + ) + { + Hash[x++] = field.name; + Hash[x++] = field.value; + } + } + } + + // jsrsPOST = true; + // jsrsExecute( '<% $action %>', <%$key%>myCallback, 'start_job', Hash ); + + //alert('start_job( ' + Hash + ', <%$key%>myCallback )' ); + //alert('start_job()' ); + <%$key%>start_job( Hash, <%$key%>myCallback ); + +} + +function <%$key%>myCallback( jobnum ) { + + overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + +} + +</SCRIPT> diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html new file mode 100644 index 000000000..8f2ff1982 --- /dev/null +++ b/httemplate/elements/progress-popup.html @@ -0,0 +1,105 @@ +% +% my $jobnum = $cgi->param('jobnum'); +% my $url = $cgi->param('url'); +% my $message = $cgi->param('message'); +% my $formname = scalar($cgi->param('formname')); +% + +<HTML> + <HEAD> + <TITLE></TITLE> + </HEAD> + <BODY BGCOLOR="#ccccff" onLoad="refreshStatus()"> + +<% include('/elements/xmlhttp.html', + 'url' => $p.'elements/jsrsServer.html', + 'subs' => [ 'job_status' ], + ) +%> +<SCRIPT TYPE="text/javascript" src="../elements/qlib/control.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" src="../elements/qlib/imagelist.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" src="../elements/qlib/progress.js"></SCRIPT> +<SCRIPT TYPE="text/javascript"> +function refreshStatus () { + //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' ); + + job_status( '<% $jobnum %>', updateStatus ); +} +function updateStatus( status_statustext ) { + + //var Array = status_statustext.split("\n"); + var statusArray = eval('(' + status_statustext + ')'); + var status = statusArray[0]; + var statustext = statusArray[1]; + + //if ( status == 'progress' ) { + //IE workaround, no i have no idea why + if ( status.indexOf('progress') > -1 ) { + document.getElementById("progress_percent").innerHTML = statustext + '%'; + bar1.set(statustext); + bar1.update; + //jsrsExecute( '<%$p%>elements/jsrsServer.html', updateStatus, 'job_status', '<% $jobnum %>' ); + job_status( '<% $jobnum %>', updateStatus ); + } else if ( status.indexOf('complete') > -1 ) { +% if ( $message ) { + + document.getElementById("progress_message").innerHTML = "<% $message %>"; + document.getElementById("progress_bar").innerHTML = ''; + document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">'; + document.getElementById("progress_jobnum").innerHTML = ''; + if ( parent.document.<%$formname%>.submit.disabled == true ) { + parent.document.<%$formname%>.submit.disabled=false; + } +% } elsif ( $url ) { + + window.top.location.href = '<% $url %>'; +% } else { + + alert('job done but no url or message specified'); +% } + + } else if ( status.indexOf('error') > -1 ) { + document.getElementById("progress_message").innerHTML = '<FONT SIZE="+1" COLOR="#FF0000">Error: ' + statustext + '</FONT>'; + document.getElementById("progress_bar").innerHTML = ''; + document.getElementById("progress_percent").innerHTML = '<INPUT TYPE="button" VALUE="OK" onClick="parent.nd(1);">'; + document.getElementById("progress_jobnum").innerHTML = ''; + if ( parent.document.<%$formname%>.submit.disabled == true ) { + parent.document.<%$formname%>.submit.disabled=false; + } + } else { + alert('XXX unknown status returned from server: ' + status); + } + +} +</SCRIPT> + + <TABLE WIDTH="100%"> + <TR> + <TD ALIGN="center" ID="progress_message"> + Server processing job... + </TD> + </TR><TR> + <TD ALIGN="center" ID="progress_bar"> + <SCRIPT TYPE="text/javascript"> + // Create imagelist + SEGS = new QImageList(4, 23, "../images/progressbar-empty.png", "../images/progressbar-full.png"); + // Create bars + bar1 = new QProgress(null, "bar1", SEGS, 100); + // bar1.set(0); + // bar1.update; + </SCRIPT> + </TD> + </TR><TR> + <TD ALIGN="center"> + <DIV ID="progress_percent">%</DIV> + </TD> + </TR><TR> + <TD ALIGN="center" ID="progress_jobnum"> + (progress of job #<% $jobnum %>) + </TD> + </TR> + </TABLE> + + </BODY> +</HTML> + diff --git a/httemplate/elements/qlib/box.js b/httemplate/elements/qlib/box.js new file mode 100644 index 000000000..537aac4c8 --- /dev/null +++ b/httemplate/elements/qlib/box.js @@ -0,0 +1,29 @@ +/**
+ * QLIB 1.0 Box Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QBox(parent, name, res, x, y, width, height, body, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ if (this.res = res) {
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = width - 0;
+ this.height = (typeof(height) == "number") ? height : null;
+ this.body = body || " ";
+ var j = QBox.arguments.length;
+ this.visible = (j > 8) ? visible : true;
+ this.effects = (j > 9) ? effects : (res.effects || 0);
+ this.opacity = (j > 10) ? opacity : (res.opacity != null ? res.opacity : 100);
+ this.zindex = (j > 11) ? zindex : null;
+ this.create();
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QBox.prototype = new QBoxCtrl();
diff --git a/httemplate/elements/qlib/boxctrl.js b/httemplate/elements/qlib/boxctrl.js new file mode 100644 index 000000000..417b204e4 --- /dev/null +++ b/httemplate/elements/qlib/boxctrl.js @@ -0,0 +1,48 @@ +/**
+ * QLIB 1.0 Box Abstraction
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QBoxCtrl_content() {
+ with (this) {
+ if (res) {
+ this.cwidth = width - res.L - res.R - 8;
+ this.cheight = height && (height - res.T - res.B - 8);
+ var ec = '"><table border="0" cellspacing="0" cellpadding="0"><tr><td></td></tr></table></td>';
+ document.write('<table class="qbox" border="0" cellspacing="0" cellpadding="0" width="' +
+ (width - 8) + (height != null ? '" height="' + (height - 8) : '') + '"><tr><td width="' +
+ res.L + '" height="' + res.T + '"><img src="' + res.TL.src + '" border="0" width="' +
+ res.L + '" height="' + res.T + '"></td><td width="' + cwidth + '" height="' + res.T +
+ '" background="' + res.TC.src + ec + '<td width="' + res.R + '" height="' + res.T +
+ '"><img src="' + res.TR.src + '" border="0" width="' + res.R + '" height="' + res.T +
+ '"></td></tr><tr><td width="' + res.L + (cheight != null ? '" height="' + cheight : '') +
+ '" background="' + res.ML.src + ec + '<td width="' + cwidth + '" bgcolor="' + res.bgcolor +
+ (cheight != null ? '" height="' + cheight : '') + (res.bgtile ? '" background="' +
+ res.bgtile.src : '') + '" align="left" valign="top" class="body" unselectable="on">');
+ if (typeof(body) == "function") {
+ this.body();
+ } else {
+ document.write(body);
+ }
+ document.write('</td><td width="' + res.R + (cheight != null ? '" height="' + cheight : '') +
+ '" background="' + res.MR.src + ec + '</tr><tr><td width="' + res.L + '" height="' + res.B +
+ '"><img src="' + res.BL.src + '" border="0" width="' + res.L + '" height="' + res.B +
+ '"></td><td width="' + cwidth + '" height="' + res.B + '" background="' + res.BC.src + ec +
+ '<td width="' + res.R + '" height="' + res.B + '"><img src="' + res.BR.src +
+ '" border="0" width="' + res.R + '" height="' + res.B + '"></td></tr></table><br>');
+ }
+ }
+}
+
+function QBoxCtrl() {
+ this.res = false;
+ this.body = " ";
+ this.cwidth = this.cheight = 0;
+ this.content = QBoxCtrl_content;
+}
+QBoxCtrl.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/boxres.js b/httemplate/elements/qlib/boxres.js new file mode 100644 index 000000000..087817211 --- /dev/null +++ b/httemplate/elements/qlib/boxres.js @@ -0,0 +1,42 @@ +/**
+ * QLIB 1.0 Box Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QBoxRes(t, r, b, l, tc, tr, mr, br, bc, bl, ml, tl, bgcolor, bgtile, effects, opacity) {
+ var args = QBoxRes.arguments.length;
+ this.T = t;
+ this.R = r;
+ this.B = b;
+ this.L = l;
+ this.TC = new Image();
+ this.TC.src = tc;
+ this.TR = new Image(r, t);
+ this.TR.src = tr;
+ this.MR = new Image();
+ this.MR.src = mr;
+ this.BR = new Image(r, b);
+ this.BR.src = br;
+ this.BC = new Image();
+ this.BC.src = bc;
+ this.BL = new Image(l, b);
+ this.BL.src = bl;
+ this.ML = new Image();
+ this.ML.src = ml;
+ this.TL = new Image(l, t);
+ this.TL.src = tl;
+ this.bgcolor = bgcolor || "#FFFFFF";
+ if (bgtile) {
+ this.bgtile = new Image();
+ this.bgtile.src = bgtile;
+ } else {
+ this.bgtile = false;
+ }
+ this.effects = (args > 13) ? effects : null;
+ this.opacity = (args > 14) ? opacity : null;
+}
diff --git a/httemplate/elements/qlib/button.js b/httemplate/elements/qlib/button.js new file mode 100644 index 000000000..05247d5f8 --- /dev/null +++ b/httemplate/elements/qlib/button.js @@ -0,0 +1,74 @@ +/**
+ * QLIB 1.0 Button Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QButton_update() {
+ with (this) {
+ image.src = ((!enabled && res.imgD) || (value ? res.imgP : res.imgN)).src;
+ }
+}
+
+function QButton_doEvent() {
+ with (this) {
+ if (enabled) {
+ if (res.style == 1) {
+ this.value = value ? 0 : 1;
+ update();
+ }
+ onClick(value, tag);
+ }
+ }
+ return false;
+}
+
+function QButton_enable(state) {
+ this.enabled = state;
+ this.update();
+}
+
+function QButton_set(value) {
+ if (this.enabled) {
+ this.value = value ? 1 : 0;
+ this.update();
+ }
+ return true;
+}
+
+function QButton(parent, name, res, tooltip) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.tip = tooltip || "";
+ this.enabled = true;
+ this.value = 0;
+ this.set = QButton_set;
+ this.enable = QButton_enable;
+ this.update = QButton_update;
+ this.doEvent = QButton_doEvent;
+ this.onClick = QControl.event;
+ with (this) {
+ document.write('<a href="#" hidefocus="true" unselectable="on"' +
+ (tip ? ' title="' + tip + '"' : '') + ' onClick="return ' + name +
+ '.doEvent()" onMouseOver="' + (res.style == 2 ? name + '.set(1);' : '') +
+ 'window.top.status=' + name + '.tip;return true" onMouseOut="' +
+ (!res.style || (res.style == 2) ? name + '.set();' : '') + 'window.top.status=\'\'"' +
+ (!res.style ? ' onMouseDown="return ' + name + '.set(1)" onMouseUp="return ' + name + '.set()"' : '') +
+ '><img class="qbutton" name="' + id + '" src="' + res.imgN.src + '" border="0" width="' +
+ res.width + '" height="' + res.height + '"></a>');
+ this.image = document.images[id] || new Image(1, 1);
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QButton.prototype = new QControl();
+QButton.NORMAL = 0;
+QButton.CHECKBOX = 1;
+QButton.WEB = 2;
+QButton.SIGNAL = 3;
diff --git a/httemplate/elements/qlib/buttonres.js b/httemplate/elements/qlib/buttonres.js new file mode 100644 index 000000000..97f6dfccc --- /dev/null +++ b/httemplate/elements/qlib/buttonres.js @@ -0,0 +1,23 @@ +/**
+ * QLIB 1.0 Button Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QButtonRes(style, width, height, normal, pressed, disabled) {
+ this.style = style;
+ this.width = width;
+ this.height = height;
+ this.imgN = new Image(width, height);
+ this.imgN.src = normal;
+ this.imgP = new Image(width, height);
+ this.imgP.src = pressed;
+ if (disabled) {
+ this.imgD = new Image(width, height);
+ this.imgD.src = disabled;
+ }
+}
diff --git a/httemplate/elements/qlib/control.js b/httemplate/elements/qlib/control.js new file mode 100644 index 000000000..f50206e27 --- /dev/null +++ b/httemplate/elements/qlib/control.js @@ -0,0 +1,51 @@ +/**
+ * QLIB 1.0 Base Abstract Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QControl_init(parent, name) {
+ this.parent = parent || self;
+ this.window = (parent && parent.window) || self;
+ this.document = (parent && parent.document) || self.document;
+ this.name = (parent && parent.name) ? (parent.name + "." + name) : ("self." + name);
+ this.id = "Q";
+ var h = this.hash(this.name);
+ for (var j=0; j<8; j++) {
+ this.id += QControl.HEXTABLE.charAt(h & 15);
+ h >>>= 4;
+ }
+}
+
+function QControl_hash(str) {
+ var h = 0;
+ if (str) {
+ for (var j=str.length-1; j>=0; j--) {
+ h ^= QControl.ANTABLE.indexOf(str.charAt(j)) + 1;
+ for (var i=0; i<3; i++) {
+ var m = (h = h<<7 | h>>>25) & 150994944;
+ h ^= m ? (m == 150994944 ? 1 : 0) : 1;
+ }
+ }
+ }
+ return h;
+}
+
+function QControl_nop() {
+}
+
+function QControl() {
+ this.init = QControl_init;
+ this.hash = QControl_hash;
+ this.window = self;
+ this.document = self.document;
+ this.tag = null;
+}
+QControl.ANTABLE = "w5Q2KkFts3deLIPg8Nynu_JAUBZ9YxmH1XW47oDpa6lcjMRfi0CrhbGSOTvqzEV";
+QControl.HEXTABLE = "0123456789ABCDEF";
+QControl.nop = QControl_nop;
+QControl.event = QControl_nop;
diff --git a/httemplate/elements/qlib/counter.js b/httemplate/elements/qlib/counter.js new file mode 100644 index 000000000..72aeddbdb --- /dev/null +++ b/httemplate/elements/qlib/counter.js @@ -0,0 +1,81 @@ +/**
+ * QLIB 1.0 Animated Digital Counter
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QCounter_update() {
+ with (this) {
+ var v = Math.max(value, 0);
+ var mod;
+ for (var j=0; j<size; j++) {
+ mod = Math.floor(v % 10);
+ images[j].src = (v >= 1) || (!j) ? res.list[mod].src : res.list[10].src;
+ v /= 10;
+ }
+ }
+}
+
+function QCounter_count(value, step) {
+ this._cntt = false;
+ this.value += step;
+ if ((step * (this.value - value)) >= 0) {
+ this.value = value - 0; // convert to number
+ } else {
+ this._cntt = setTimeout(this.name + ".count(" + value + "," + step + ")", 50);
+ }
+ this.update();
+}
+
+function QCounter_set(value) {
+ this.setval = value;
+ if (value != this.value) {
+ if (this._cntt) {
+ clearTimeout(this._cntt);
+ this._cntt = false;
+ }
+ var dv = value - this.value;
+ if (this.effect == 2) {
+ dv = dv / Math.min(10, Math.abs(dv));
+ } else if (this.effect == 3) {
+ dv = dv / Math.abs(dv);
+ }
+ this.count(value, dv);
+ }
+}
+
+function QCounter(parent, name, res, size, effect) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.setval = this.value = 0;
+ this.size = size || 4;
+ this.effect = effect || 2;
+ this._cntt = false;
+ this.images = new Array(this.size);
+ this.set = QCounter_set;
+ this.update = QCounter_update;
+ this.count = QCounter_count;
+ with (this) {
+ document.write('<table class="qcounter" width="' + (res.width * size) + '" height="' + res.height +
+ '" border="0" cellspacing="0" cellpadding="0" unselectable="on"><tr>');
+ for (var j=(size - 1); j>=0; j--) {
+ document.write('<td width="' + res.width + '" height="' + res.height +
+ '" unselectable="on"><img name="' + id + j + '" src="' + (j ? res.list[10].src : res.list[0].src) +
+ '" border="0" width="' + res.width + '" height="' + res.height + '"></td>');
+ images[j] = document.images[id + j] || new Image(1, 1);
+ }
+ document.write('</tr></table>');
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QCounter.prototype = new QControl();
+QCounter.INSTANT = 1;
+QCounter.FAST = 2;
+QCounter.SLOW = 3;
diff --git a/httemplate/elements/qlib/imagelist.js b/httemplate/elements/qlib/imagelist.js new file mode 100644 index 000000000..9f12de053 --- /dev/null +++ b/httemplate/elements/qlib/imagelist.js @@ -0,0 +1,25 @@ +/**
+ * QLIB 1.0 ImageList Resource
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QImageList(width, height) {
+ var len = QImageList.arguments.length - 2;
+ if (len > 0) {
+ this.list = new Array(len);
+ this.length = len;
+ this.width = width;
+ this.height = height;
+ var im;
+ for (var j=0; j<len; j++) {
+ im = new Image(width, height);
+ im.src = QImageList.arguments[j + 2];
+ this.list[j] = im;
+ }
+ }
+}
\ No newline at end of file diff --git a/httemplate/elements/qlib/label.js b/httemplate/elements/qlib/label.js new file mode 100644 index 000000000..2d8b1e710 --- /dev/null +++ b/httemplate/elements/qlib/label.js @@ -0,0 +1,72 @@ +/**
+ * QLIB 1.0 Text Label
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QLabel_set_ie(value) {
+ this.label.innerText = (this.value = value) || "\xA0";
+}
+
+function QLabel_set_dom2(value) {
+ with (this.label) {
+ replaceChild(this.document.createTextNode((this.value = value) || "\xA0"), firstChild);
+ }
+}
+
+function QLabel_set_ns4(value) {
+ this.value = value || "";
+ with (this) {
+ document.open();
+ document.write('<div class="qlabel">' + (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' +
+ name + '.doEvent()" onMouseOut="window.top.status=\'\'" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true">' + value + '</a>' : value) + '</div>');
+ document.close();
+ }
+}
+
+function QLabel_doEvent() {
+ this.onClick(this.value, this.tag);
+ return false;
+}
+
+function QLabel(parent, name, value, clickable, tooltip) {
+ this.init(parent, name);
+ this.value = value || "";
+ this.clickable = clickable || false;
+ this.tooltip = tooltip || "";
+ this.doEvent = QLabel_doEvent;
+ this.onClick = QControl.event;
+ with (this) {
+ if (document.getElementById || document.all) {
+ document.write(clickable ? '<div class="qlabel" unselectable="on"><a id="' + id + '" href="#" title="' +
+ tooltip + '" onClick="return ' + name + '.doEvent()" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true" onMouseOut="window.top.status=\'\'" hidefocus="true" unselectable="on">' +
+ (value || ' ') + '</a></div>' : '<div id="' + id + '" class="qlabel" unselectable="on">' +
+ (value || ' ') + '</div>');
+ this.label = document.getElementById ? document.getElementById(id) :
+ (document.all.item ? document.all.item(id) : document.all[id]);
+ this.set = (label && (label.innerText ? QLabel_set_ie :
+ (label.replaceChild && QLabel_set_dom2))) || QControl.nop;
+ } else if (document.layers) {
+ var suffix = "";
+ for (var j=value.length; j<QLabel.TEXTQUOTA; j++) suffix += " ";
+ document.write('<div><ilayer id="i' + id + '"><layer id="' + id + '"><div class="qlabel">' +
+ (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' + name +
+ '.doEvent()" onMouseOver="window.top.status=' + name +
+ '.tooltip;return true" onMouseOut="window.top.status=\'\'">' + value + suffix + '</a>' :
+ value + suffix) + '</div></layer></ilayer></div>');
+ this.label = (this.label = document.layers["i" + id]) && label.document.layers[id];
+ this.document = label && label.document;
+ this.set = (label && document) ? QLabel_set_ns4 : QControl.nop;
+ } else {
+ document.write("Object is not supported");
+ }
+ }
+}
+QLabel.prototype = new QControl();
+QLabel.TEXTQUOTA = 50;
diff --git a/httemplate/elements/qlib/messagebox.js b/httemplate/elements/qlib/messagebox.js new file mode 100644 index 000000000..2e458393d --- /dev/null +++ b/httemplate/elements/qlib/messagebox.js @@ -0,0 +1,57 @@ +/**
+ * QLIB 1.0 Message Box Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QMessageBox_alert(msg) {
+ if (typeof(msg) == "string") {
+ this.label.set(this.value = msg);
+ }
+ this.center();
+ this.focus();
+ this.show(true);
+}
+
+function QMessageBox_close() {
+ with (this.parent) {
+ if (!onClose(tag)) show(false);
+ }
+}
+
+function QMessageBox_body() {
+ with (this) {
+ document.write('<table border="0" width="' + cwidth + '"><tr><td align="left" valign="top" unselectable="on">');
+ this.label = new QLabel(this, "label", value);
+ document.write('</td></tr><tr><td height="' + (bres.height + 14) + '" align="center" valign="bottom" unselectable="on">');
+ this.button = new QButton(this, "button", bres, "Close");
+ document.write('</td></tr></table>');
+ button.onClick = QMessageBox_close;
+ }
+}
+
+function QMessageBox(parent, name, box, btn, msg, effects, opacity) {
+ this.init(parent, name);
+ if ((this.res = box) && (this.bres = btn)) {
+ this.value = typeof(msg) == "string" ? msg : "";
+ this.width = Math.max(200, Math.floor(Math.sqrt(555 * this.value.length)));
+ this.height = null;
+ this.x = this.y = 0;
+ this.visible = false;
+ this.zindex = null;
+ this.body = QMessageBox_body;
+ var j = QMessageBox.arguments.length;
+ this.effects = j > 5 ? effects : (box.effects != null ? box.effects : 0);
+ this.opacity = j > 6 ? opacity : (box.opacity != null ? box.opacity : 100);
+ this.create();
+ this.alert = QMessageBox_alert;
+ this.onClose = QControl.event;
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QMessageBox.prototype = new QBoxCtrl();
diff --git a/httemplate/elements/qlib/progress.js b/httemplate/elements/qlib/progress.js new file mode 100644 index 000000000..2de077eac --- /dev/null +++ b/httemplate/elements/qlib/progress.js @@ -0,0 +1,73 @@ +/**
+ * QLIB 1.0 Progress Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QProgress_update() {
+ with (this) {
+ var i = low;
+ for (var j=0; j<size; j++) {
+ images[j].src = i < value ? imgsrc1 : imgsrc0;
+ i += delta;
+ }
+ }
+}
+
+function QProgress_set(value) {
+ this.value = value - 0;
+ this.update();
+}
+
+function QProgress_setBounds(low, high) {
+ this.low = Math.min(low, high);
+ this.high = Math.max(low, high);
+ this.delta = (this.high - this.low) / this.size;
+ this.update();
+}
+
+function QProgress(parent, name, res, size, style) {
+ this.init(parent, name);
+ if (res) {
+ this.res = res;
+ this.value = 0;
+ this.low = 0;
+ this.high = 100;
+ this.size = size || 10;
+ this.delta = 100 / this.size;
+ this.style = style || 0;
+ this.images = new Array(this.size);
+ this.imgsrc0 = res.list[0] && res.list[0].src;
+ this.imgsrc1 = res.list[1] && res.list[1].src;
+ this.set = QProgress_set;
+ this.update = QProgress_update;
+ this.setBounds = QProgress_setBounds;
+ with (this) {
+ var hor = this.style < 2;
+ var rev = this.style % 2;
+ document.write('<table class="qprogress" border="0" cellspacing="0" cellpadding="0" unselectable="on" ' +
+ (hor ? 'width="' + (size * res.width) + '" height="' + res.height + '"><tr>' : 'width="' + res.width +
+ '" height="' + (size * res.height) + '">'));
+ for (var j=0; j<size; j++) {
+ document.write((hor ? '' : '<tr>') + '<td width="' + res.width + '" height="' + res.height +
+ '" unselectable="on"><img name="' + id + (rev ? size - j - 1 : j) + '" src="' + res.list[0].src +
+ '" border="0" width="' + res.width + '" height="' + res.height + '"></td>' + (hor ? '' : '</tr>'));
+ }
+ document.write((hor ? '</tr>' : '') + '</table>');
+ for (var j=0; j<size; j++) {
+ images[j] = document.images[id + j] || new Image(1, 1);
+ }
+ }
+ } else {
+ this.document.write("invalid resource");
+ }
+}
+QProgress.prototype = new QControl();
+QProgress.NORMAL = 0;
+QProgress.REVERSE = 1;
+QProgress.FALL = 2;
+QProgress.RISE = 3;
diff --git a/httemplate/elements/qlib/sound.js b/httemplate/elements/qlib/sound.js new file mode 100644 index 000000000..3d1aaf660 --- /dev/null +++ b/httemplate/elements/qlib/sound.js @@ -0,0 +1,47 @@ +/**
+ * QLIB 1.0 Preloaded Sound
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QSound_play(loop) {
+ this._out.loop = loop || 0;
+ this._out.src = this._buf.src;
+}
+
+function QSound_stop() {
+ this._out.loop = 0;
+ this._out.src = "";
+}
+
+function QSound_setVolume(volume) {
+ this._out.volume = this.volume = volume;
+}
+
+function QSound(parent, name, src, volume) {
+ this.init(parent, name);
+ this.volume = volume || 0;
+ this.play = this.stop = this.setVolume = QControl.nop;
+ with (this) {
+ document.write('<bgsound id="' + id + '" src="" volume="' + volume + '">');
+ if (document.all && document.all.item) {
+ this._out = document.all.item(id);
+ if (_out && (typeof _out.src != "undefined") && (_out.volume === volume)) {
+ document.write('<bgsound id="b' + id + '" src="' + src + '" volume="-10000">');
+ this._buf = document.all.item("b" + id);
+ if (_buf) {
+ this.play = QSound_play;
+ this.stop = QSound_stop;
+ this.setVolume = QSound_setVolume;
+
+ _out.onreadystatechange = new Function("alert(0)");
+ }
+ }
+ }
+ }
+}
+QSound.prototype = new QControl();
diff --git a/httemplate/elements/qlib/sprite.js b/httemplate/elements/qlib/sprite.js new file mode 100644 index 000000000..72a68fb7c --- /dev/null +++ b/httemplate/elements/qlib/sprite.js @@ -0,0 +1,125 @@ +/**
+ * QLIB 1.0 Sprite Object
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QSprite_load(src) {
+ if (src) {
+ this.face = new Image(this.cwidth, this.cheight);
+ this.face.src = src;
+ this.valid = false;
+ }
+}
+
+function QSprite_show(show) {
+ if (show && !this.valid && this.face.complete) {
+ this._img.src = this.face.src;
+ this.valid = true;
+ }
+ this._show(show);
+}
+
+function QSprite_moveTo(x, y) {
+ this.stop();
+ this._move(x, y);
+}
+
+function QSprite_slideTo(x, y) {
+ this.stop();
+ if (this.visible) {
+ this.doSlide(++this._spro, x, y);
+ } else {
+ this.moveTo(x, y);
+ }
+}
+
+function QSprite_shake() {
+ this.stop();
+ if (this.visible) {
+ this.doShake(++this._spro, 0, this.x, this.y);
+ }
+}
+
+function QSprite_stop() {
+ this._spro++;
+ if (this._sprt) {
+ clearTimeout(this._sprt);
+ this._sprt = false;
+ }
+}
+
+function QSprite_doSlide(id, x, y) {
+ if (this._spro == id) {
+ this._sprt = false;
+ var dx = Math.round(x - this.x);
+ var dy = Math.round(y - this.y);
+ if (dx || dy) {
+ if (dx) dx = dx > 0 ? Math.ceil(dx/4) : Math.floor(dx/4);
+ if (dy) dy = dy > 0 ? Math.ceil(dy/4) : Math.floor(dy/4);
+ this._move(this.x + dx, this.y + dy);
+ this._sprt = setTimeout(this.name + ".doSlide(" + id + "," + x + "," + y + ")", 30);
+ } else {
+ this._move(x, y);
+ }
+ }
+}
+
+function QSprite_doShake(id, phase, x, y) {
+ if (this._spro == id) {
+ this._sprt = false;
+ if (phase < 20) {
+ var m = 3 * Math.sin(.16 * phase);
+ this._move(x + m * Math.sin(phase), y + m * Math.cos(phase));
+ this._sprt = setTimeout(this.name + ".doShake(" + id + "," + (++phase) + "," + x + "," + y + ")", 20);
+ } else {
+ this._move(x, y);
+ }
+ }
+}
+
+function QSprite_doClick() {
+ if (!this._sprt) {
+ this.onClick(this.tag);
+ }
+ return false;
+}
+
+function QSprite(parent, name, x, y, width, height, src, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = (this.cwidth = width - 0) + 8;
+ this.height = (this.cheight = height - 0) + 8;
+ var j = QSprite.arguments.length;
+ this.visible = (j > 7) ? visible : true;
+ this.effects = (j > 8) ? effects : 0;
+ this.opacity = (j > 9) ? opacity : 100;
+ this.zindex = (j > 10) ? zindex : null;
+ this.valid = !!src;
+ this.content = '<a href="#" title="" onclick="return false" onmousedown="return ' + this.name +
+ '.doClick()" onmouseover="window.top.status=\'\';return true" hidefocus="true" unselectable="on"><img name="' +
+ this.id + '" src="' + (src || '') + '" border="0" width="' + this.cwidth + '" height="' + this.cheight +
+ '" alt="" unselectable="on"></a>';
+ this.doClick = QSprite_doClick;
+ this.doSlide = QSprite_doSlide;
+ this.doShake = QSprite_doShake;
+ this.onClick = QControl.event;
+ this.create();
+ this.face = this._img = this.document.images[this.id] || new Image(1, 1);
+ this._spro = 0;
+ this._sprt = false;
+ this._show = this.show;
+ this._move = this.moveTo;
+ this.load = QSprite_load;
+ this.show = QSprite_show;
+ this.moveTo = QSprite_moveTo;
+ this.slideTo = QSprite_slideTo;
+ this.shake = QSprite_shake;
+ this.stop = QSprite_stop;
+}
+QSprite.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/window.js b/httemplate/elements/qlib/window.js new file mode 100644 index 000000000..6056fda9b --- /dev/null +++ b/httemplate/elements/qlib/window.js @@ -0,0 +1,25 @@ +/**
+ * QLIB 1.0 Window Control
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QWindow(parent, name, x, y, width, height, content, visible, effects, opacity, zindex) {
+ this.init(parent, name);
+ this.x = x - 0;
+ this.y = y - 0;
+ this.width = width - 0;
+ this.height = (typeof(height) == "number") ? height : null;
+ this.content = content;
+ var j = QWindow.arguments.length;
+ this.visible = (j > 7) ? visible : true;
+ this.effects = (j > 8) ? effects : 0;
+ this.opacity = (j > 9) ? opacity : 100;
+ this.zindex = (j > 10) ? zindex : null;
+ this.create();
+}
+QWindow.prototype = new QWndCtrl();
diff --git a/httemplate/elements/qlib/wndctrl.js b/httemplate/elements/qlib/wndctrl.js new file mode 100644 index 000000000..b3bde4e92 --- /dev/null +++ b/httemplate/elements/qlib/wndctrl.js @@ -0,0 +1,322 @@ +/**
+ * QLIB 1.0 Window Abstraction
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * http://qlib.quazzle.com
+ */
+
+function QWndCtrl_center_ie4() {
+ var b = this.document.body;
+ this.moveTo(b.scrollLeft + Math.max(0, Math.floor((b.clientWidth -
+ this.width) / 2)), b.scrollTop + 100);
+}
+
+function QWndCtrl_center_moz() {
+ this.moveTo(self.pageXOffset + Math.max(0, Math.floor((self.innerWidth -
+ this.width) / 2)), self.pageYOffset + 100);
+}
+
+function QWndCtrl_setEffects_ie4(fx) {
+ this.effects = fx;
+ with (this.wnd) {
+ filters[0].enabled = (fx & 256) != 0;
+ filters[1].enabled = (fx & 512) != 0;
+ filters[2].enabled = (fx & 1024) != 0;
+ filters[4].enabled = (fx & 2048) != 0;
+ }
+}
+
+function QWndCtrl_setEffects_moz(fx) {
+ this.effects = fx;
+}
+
+function QWndCtrl_setOpacity_ie4(op) {
+ this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));
+ this.wnd.filters[3].opacity = this.opacity;
+ this.wnd.filters[3].enabled = (this.opacity < 100);
+}
+
+function QWndCtrl_setOpacity_moz(op) {
+ this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));
+ this.wnd.style.MozOpacity = this.opacity + "%";
+}
+
+function QWndCtrl_setSize_css(w, h) {
+ this.wnd.style.width = (this.width = Math.floor(w - 0)) + "px";
+ this.wnd.style.height = typeof(h) == "number" ? (this.height = Math.floor(h)) + "px" : "auto";
+}
+
+function QWndCtrl_setSize_ns4(w, h) {
+ this.wnd.clip.width = this.width = Math.floor(w - 0);
+ if (typeof(h) == "number") {
+ this.wnd.clip.height = this.height = Math.floor(h);
+ }
+}
+
+function QWndCtrl_focus() {
+ this.setZIndex(QWndCtrl.TOPZINDEX++);
+}
+
+function QWndCtrl_setZIndex_css(z) {
+ this.wnd.style.zIndex = this.zindex = z || 0;
+}
+
+function QWndCtrl_setZIndex_ns4(z) {
+ this.wnd.zIndex = this.zindex = z || 0;
+}
+
+function QWndCtrl_moveTo_css(x, y) {
+ this.wnd.style.left = (this.x = Math.floor(x - 0)) + "px";
+ this.wnd.style.top = (this.y = Math.floor(y - 0)) + "px";
+}
+
+function QWndCtrl_moveTo_ns4(x, y) {
+ this.wnd.moveTo(this.x = Math.floor(x - 0), this.y = Math.floor(y - 0));
+}
+
+function QWndCtrl_fxhandler() {
+ this.fxhandler = QControl.nop;
+ this.onShow(this.visible, this.tag);
+}
+
+function QWndCtrl_show_ie4(show) {
+ if (this.visible != show) {
+ var fx = false;
+ switch (show ? this.effects & 15 : (this.effects & 240) >>> 4) {
+ case 1:
+ fx = this.wnd.filters[5];
+ break;
+ case 2:
+ (fx = this.wnd.filters[6]).transition = show ? 1 : 0;
+ break;
+ case 3:
+ (fx = this.wnd.filters[6]).transition = show ? 3 : 2;
+ break;
+ case 4:
+ (fx = this.wnd.filters[6]).transition = show ? 5 : 4;
+ break;
+ case 5:
+ (fx = this.wnd.filters[6]).transition = show ? 14 : 13;
+ break;
+ case 6:
+ (fx = this.wnd.filters[6]).transition = show ? 16 : 15;
+ break;
+ case 7:
+ (fx = this.wnd.filters[6]).transition = 12;
+ break;
+ case 8:
+ (fx = this.wnd.filters[6]).transition = 8;
+ break;
+ case 9:
+ (fx = this.wnd.filters[6]).transition = 9;
+ }
+ if (fx) {
+ fx.apply();
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.fxhandler = QWndCtrl_fxhandler;
+ fx.play(0.3);
+ } else {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_fade_moz(op, step) {
+ this._wndt = false;
+ if (step) {
+ op += step;
+ if ((op > 0) && (op < this.opacity)) {
+ this.wnd.style.MozOpacity = op + "%";
+ this._wndt = setTimeout(this.name + ".fade(" + op + "," + step + ")", 50);
+ } else {
+ if (op <= 0) {
+ this.wnd.style.visibility = "hidden";
+ this.visible = false;
+ }
+ this.wnd.style.MozOpacity = this.opacity + "%";
+ this.onShow(this.visible, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_show_moz(show) {
+ if (this.visible != show) {
+ if (this._wndt) {
+ clearTimeout(this._wndt);
+ this._wndt = false;
+ }
+ var step = show ? ((this.effects & 15) == 1) && Math.floor(this.opacity / 5) :
+ ((this.effects & 240) == 16) && -Math.floor(this.opacity / 5);
+ if (step) {
+ if (this.visible) {
+ this.fade(this.opacity - 0, step);
+ } else {
+ this.wnd.style.MozOpacity = "0%";
+ this.wnd.style.visibility = "visible";
+ this.visible = true;
+ this.fade(0, step);
+ }
+ } else {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+ }
+}
+
+function QWndCtrl_show_css(show) {
+ if (this.visible != show) {
+ this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";
+ this.onShow(show, this.tag);
+ }
+}
+
+function QWndCtrl_show_ns4(show) {
+ if (this.visible != show) {
+ this.wnd.visibility = (this.visible = show) ? "show" : "hidden";
+ this.onShow(show, this.tag);
+ }
+}
+
+function QWndCtrl_create_dom2() {
+ with (this) {
+ this.fxhandler = QControl.nop;
+ var ie4 = document.body && document.body.filters;
+ var moz = document.body && document.body.style &&
+ typeof(document.body.style.MozOpacity) == "string";
+ document.write('<div unselectable="on" id="' + id +
+ (ie4 ? '" onfilterchange="' + name + '.fxhandler()': '') +
+ '" style="position:absolute;left:' + x + 'px;top:' + y +
+ 'px;width:' + width + (height != null ? 'px;height:' + height : '') +
+ 'px;visibility:' + (visible ? 'visible' : 'hidden') +
+ ';overflow:hidden' + (zindex ? ';z-index:' + zindex : '') +
+ (ie4 ? ';filter:Gray(enabled=' + (effects & 256 ? '1' : '0') +
+ ') Xray(enabled=' + (effects & 512 ? '1' : '0') +
+ ') Invert(enabled=' + (effects & 1024 ? '1' : '0') +
+ ') alpha(enabled=' + (opacity < 100 ? '1' : '0') + ',opacity=' + opacity +
+ ') shadow(enabled=' + (effects & 2048 ? '1' : '0') +
+ ',direction=135) BlendTrans(enabled=0) RevealTrans(enabled=0)' : '') +
+ (moz && (opacity < 100) ? ';-moz-opacity:' + opacity + '%' : '') +
+ '"><div unselectable="on" class="qwindow">');
+ if (typeof(content) == "function") {
+ this.content();
+ } else {
+ document.write(content);
+ }
+ document.write('</div></div>');
+ if (this.wnd = document.getElementById ? document.getElementById(id) :
+ (document.all.item ? document.all.item(id) : document.all[id])) {
+ if (wnd.style) {
+ ie4 = ie4 && wnd.filters;
+ moz = moz && typeof(wnd.style.MozOpacity) == "string";
+ this.moveTo = QWndCtrl_moveTo_css;
+ this.setZIndex = QWndCtrl_setZIndex_css;
+ this.focus = QWndCtrl_focus;
+ this.setSize = QWndCtrl_setSize_css;
+ this.show = ie4 ? QWndCtrl_show_ie4 : (moz ? QWndCtrl_show_moz : QWndCtrl_show_css);
+ this.fade = moz ? QWndCtrl_fade_moz : QControl.nop;
+ this.setOpacity = ie4 ? QWndCtrl_setOpacity_ie4 : (moz ? QWndCtrl_setOpacity_moz : QControl.nop);
+ this.setEffects = ie4 ? QWndCtrl_setEffects_ie4 : (moz ? QWndCtrl_setEffects_moz : QControl.nop);
+ this.center = self.innerWidth ? QWndCtrl_center_moz :
+ (document.body && document.body.clientWidth ? QWndCtrl_center_ie4 : QControl.nop);
+ }
+ }
+ }
+}
+
+function QWndCtrl_create_ns4(finalize) {
+ with (this) {
+ if (finalize) {
+ if (_wnde) {
+ parent.window.onload = _wnde;
+ parent.window.onload();
+ }
+ document.open();
+ document.write('<div class="qwindow">');
+ this.content();
+ document.write('</div>');
+ document.close();
+ } else {
+ document.write('<layer id="' + id + '" left="' + x + '" top="' + y +
+ '" width="' + width + '" visibility="' + (visible ? 'show' : 'hidden') +
+ (height != null ? '" height="' + height + '" clip="' + width + ',' + height : '') +
+ (zindex ? '" z-index="' + zindex : '') + (typeof(content) != "function" ?
+ '"><div class="qwindow">' + content + '</div></layer>' : '"> </layer>'));
+ if (this.window = this.wnd = document.layers[id]) {
+ if (this.document = wnd.document) {
+ this.show = QWndCtrl_show_ns4;
+ this.moveTo = QWndCtrl_moveTo_ns4;
+ this.setZIndex = QWndCtrl_setZIndex_ns4;
+ this.focus = QWndCtrl_focus;
+ this.center = QWndCtrl_center_moz;
+ this.setSize = QWndCtrl_setSize_ns4;
+ if (typeof(content) == "function") {
+ this._wnde = parent.window.onload;
+ parent.window.onload = new Function(name + ".create(true)");
+ }
+ }
+ }
+ }
+ }
+}
+
+function QWndCtrl_create_na() {
+ this.document.write('Object is not supported.');
+ this.wnd = null;
+}
+
+function QWndCtrl_create() {
+ with (this) {
+ this.create = (document.getElementById || document.all) ? QWndCtrl_create_dom2 :
+ (document.layers ? QWndCtrl_create_ns4 : QWndCtrl_create_na);
+ create();
+ }
+}
+
+function QWndCtrl() {
+ this.x = this.y = 0;
+ this.width = this.height = 0;
+ this.content = "";
+ this.visible = true;
+ this.effects = 0;
+ this.opacity = 100;
+ this.zindex = null;
+ this._wndt = this._wnde = false;
+ this.create = QWndCtrl_create;
+ this.show = QControl.nop;
+ this.focus = QControl.nop;
+ this.center = QControl.nop;
+ this.moveTo = QControl.nop;
+ this.setSize = QControl.nop;
+ this.setOpacity = QControl.nop;
+ this.setEffects = QControl.nop;
+ this.setZIndex = QControl.nop;
+ this.onShow = QControl.event;
+}
+QWndCtrl.prototype = new QControl();
+QWndCtrl.TOPZINDEX = 1000;
+QWndCtrl.GRAY = 256;
+QWndCtrl.XRAY = 512;
+QWndCtrl.INVERT = 1024;
+QWndCtrl.SHADOW = 2048;
+QWndCtrl.FADEIN = 1;
+QWndCtrl.FADEOUT = 16;
+QWndCtrl.BOXIN = 2;
+QWndCtrl.BOXOUT = 32;
+QWndCtrl.CIRCLEIN = 3;
+QWndCtrl.CIRCLEOUT = 48;
+QWndCtrl.WIPEIN = 4;
+QWndCtrl.WIPEOUT = 64;
+QWndCtrl.HBARNIN = 5;
+QWndCtrl.HBARNOUT = 80;
+QWndCtrl.VBARNIN = 6;
+QWndCtrl.VBARNOUT = 96;
+QWndCtrl.DISSOLVEIN = 7;
+QWndCtrl.DISSOLVEOUT = 112;
+QWndCtrl.HBLINDSIN = 8;
+QWndCtrl.HBLINDSOUT = 128;
+QWndCtrl.VBLINDSIN = 9;
+QWndCtrl.VBLINDSOUT = 144;
diff --git a/httemplate/elements/search-cust_main.html b/httemplate/elements/search-cust_main.html new file mode 100644 index 000000000..f2b17eacb --- /dev/null +++ b/httemplate/elements/search-cust_main.html @@ -0,0 +1,164 @@ +% +% my( %opt ) = @_; +% $opt{'field_name'} ||= 'custnum'; +% +% my $cust_main = ''; +% if ( $opt{'value'} ) { +% $cust_main = qsearchs( +% 'table' => 'cust_main', +% 'hashref' => { 'custnum' => $opt{'value'} }, +% 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql, +% ); +% } +% + + +<INPUT TYPE="hidden" NAME="<% $opt{'field_name'} %>" VALUE="<% $opt{'value'} %>"> + +<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... --> + +<INPUT TYPE="text" NAME="<% $opt{'field_name'} %>_search" ID="<% $opt{'field_name'} %>_search" SIZE="32" VALUE="<% $cust_main ? $cust_main->name : '(cust #, name or company)' %>" onFocus="clearhint_<% $opt{'field_name'} %>_search(this);" onClick="clearhint_<% $opt{'field_name'} %>_search(this);" onChange="smart_<% $opt{'field_name'} %>_search(this);"> + +<SELECT NAME="<% $opt{'field_name'} %>_select" ID="<% $opt{'field_name'} %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $opt{'field_name'} %>(this);"> +</SELECT> + +<% include('/elements/xmlhttp.html', + 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi', + 'subs' => [ 'smart_search' ], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + function clearhint_<% $opt{'field_name'} %>_search (what) { + + what.style.color = '#000000'; + + if ( what.value == '(cust #, name or company)' ) + what.value = ''; + + if ( what.value.indexOf('Customer not found: ') == 0 ) + what.value = what.value.substr(20); + + } + + function smart_<% $opt{'field_name'} %>_search(what) { + + var customer = what.value; + + if ( customer == 'searching...' || customer == '' + || customer.indexOf('Customer not found: ') == 0 ) + return; + + if ( what.getAttribute('magic') == 'nosearch' ) { + what.setAttribute('magic', ''); + return; + } + + //what.value = 'searching...' + what.disabled = true; + what.style.color= '#000000'; + what.style.backgroundColor = '#dddddd'; + + var customer_select = document.getElementById('<% $opt{'field_name'} %>_select'); + + //alert("search for customer " + customer); + + function <% $opt{'field_name'} %>_search_update(customers) { + + //alert('customers returned: ' + customers); + + var customerArray = eval('(' + customers + ')'); + + what.disabled = false; + what.style.backgroundColor = '#ffffff'; + + if ( customerArray.length == 0 ) { + + what.form.<% $opt{'field_name'} %>.value = ''; + + what.value = 'Customer not found: ' + what.value; + what.style.color = '#ff0000'; + + what.style.display = ''; + customer_select.style.display = 'none'; + + } else if ( customerArray.length == 1 ) { + + //alert('one customer found: ' + customerArray[0]); + + what.form.<% $opt{'field_name'} %>.value = customerArray[0][0]; + what.value = customerArray[0][1]; + + what.style.display = ''; + customer_select.style.display = 'none'; + + } else { + + //alert('multiple customers found, have to create select dropdown'); + + //blank the current list + for ( var i = customer_select.length; i >= 0; i-- ) + customer_select.options[i] = null; + + opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000'); + + //add the multiple customers + for ( var s = 0; s < customerArray.length; s++ ) + opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000'); + + opt(customer_select, 'cancel', '(Edit search string)', '#000000'); + + what.style.display = 'none'; + customer_select.style.display = ''; + + } + + } + + smart_search( customer, <% $opt{'field_name'} %>_search_update ); + + + } + + function select_<% $opt{'field_name'} %> (what) { + + var custnum = what.options[what.selectedIndex].value; + var customer = what.options[what.selectedIndex].text; + + var customer_obj = document.getElementById('<% $opt{'field_name'} %>_search'); + + if ( custnum == '' ) { + //what.style.color = '#ff0000'; + + } else if ( custnum == 'cancel' ) { + + customer_obj.style.color = '#000000'; + + what.style.display = 'none'; + customer_obj.style.display = ''; + customer_obj.focus(); + + } else { + + what.form.<% $opt{'field_name'} %>.value = custnum; + + customer_obj.value = customer; + customer_obj.style.color = '#000000'; + + what.style.display = 'none'; + customer_obj.style.display = ''; + + } + + } + + function opt(what,value,text,color) { + var optionName = new Option(text, value, false, false); + optionName.style.color = color; + var length = what.length; + what.options[length] = optionName; + } + +</SCRIPT> + diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html new file mode 100644 index 000000000..299a66a45 --- /dev/null +++ b/httemplate/elements/select-access_group.html @@ -0,0 +1,16 @@ +% +% my( $groupnum, %opt ) = @_; +% +% %opt{'records'} = delete $opt{'access_group'} +% if $opt{'access_group'}; +% +% +<% include( '/elements/select-table.html', + 'table' => 'access_group', + 'name_col' => 'groupname', + 'value' => $groupnum, + 'empty_label' => '(none)', + #'hashref' => { 'disabled' => '' }, + %opt, + ) +%> diff --git a/httemplate/elements/select-agent.html b/httemplate/elements/select-agent.html new file mode 100644 index 000000000..e85ede5b8 --- /dev/null +++ b/httemplate/elements/select-agent.html @@ -0,0 +1,19 @@ +% +% my( $agentnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'agents'} +% if $opt{'agents'}; +% +% +<% include( '/elements/select-table.html', + 'table' => 'agent', + 'name_col' => 'agent', + 'value' => $agentnum, + 'empty_label' => 'all', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + $FS::CurrentUser::CurrentUser->agentnums_sql. + ' ORDER BY agent', + %opt, + ) +%> diff --git a/httemplate/elements/select-cust-fields.html b/httemplate/elements/select-cust-fields.html new file mode 100644 index 000000000..98feaf85c --- /dev/null +++ b/httemplate/elements/select-cust-fields.html @@ -0,0 +1,24 @@ +% +% my( $cust_fields, %opt ) = @_; +% +% use FS::ConfDefaults; +% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ]; +% +% tie my %hash, 'Tie::IxHash', @{ $opt{'avail_fields'} }; +% +% + + +<SELECT NAME="cust_fields"> + + <OPTION VALUE="">(configured default) +% +% foreach my $value ( keys %hash ) { + + + <OPTION VALUE="<% $value %>"><% $hash{$value} %> +% } + + +</SELECT> + diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html new file mode 100644 index 000000000..58f270342 --- /dev/null +++ b/httemplate/elements/select-cust_pkg-status.html @@ -0,0 +1,20 @@ +% +% my( $status, %opt ) = @_; +% +% $opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } ) +% +% + + +<SELECT NAME="status"> + + <OPTION VALUE="">all +% foreach my $status ( @{ $opt{'statuses'} } ) { + + + <OPTION VALUE="<% $status %>"><% $status %> +% } + + +</SELECT> + diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html new file mode 100644 index 000000000..34476bc94 --- /dev/null +++ b/httemplate/elements/select-month_year.html @@ -0,0 +1,62 @@ +% +% +% my %opt = @_; +% +% my $prefix = $opt{'prefix'} || ''; +% my $disabled = $opt{'disabled'} || ''; +% my $empty = $opt{'empty_option'} || ''; +% my $start_year = $opt{'start_year'}; +% my $end_year = $opt{'end_year'} || '2037'; +% +% my @mon; +% if ( $opt{'show_month_abbr'} ) { +% @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +% } else { +% @mon = ( 1 .. 12 ); +% } +% +% my $date = $opt{'selected_date'} || ''; +% $date = '' if $date eq '-'; +% #$date ||= '01-2000' unless $empty; +% +% my $mon = $opt{'selected_mon'} || 0; +% my $year = $opt{'selected_year'} || 0; +% if ( $date ) { +% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +% } else { +% die "unrecognized expiration date format: $date"; +% } +% } +% +% unless ( $start_year ) { +% my @t = localtime; +% $start_year = $t[5] + 1900; +% } +% $start_year = $year if $start_year > $year && $year > 0; +% +% + + +<SELECT NAME="<% $prefix %>_month" SIZE="1" <% $disabled%>> + +<% $empty ? '<OPTION VALUE="">' : '' %> +% foreach ( 1 .. 12 ) { + + <OPTION<% $_ == $mon ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $mon[$_-1] %> +% } + + +</SELECT>/<SELECT NAME="<% $prefix %>_year" SIZE="1" <% $disabled%>> + +<% $empty ? '<OPTION VALUE="">' : '' %> +% for ( $start_year .. $end_year ) { + + <OPTION<% $_ == $year ? ' SELECTED' : '' %> VALUE="<% $_ %>"><% $_ %> +% } + + +</SELECT> + diff --git a/httemplate/elements/select-part_referral.html b/httemplate/elements/select-part_referral.html new file mode 100644 index 000000000..efcc477af --- /dev/null +++ b/httemplate/elements/select-part_referral.html @@ -0,0 +1,18 @@ +% +% my( $refnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'part_referrals'} +% if $opt{'part_referrals'}; +% +% +<% include( '/elements/select-table.html', + 'table' => 'part_referral', + 'name_col' => 'referral', + 'value' => $refnum, + 'empty_label' => 'Select advertising source', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + FS::part_referral->all_part_referral(1), + %opt, + ) +%> diff --git a/httemplate/elements/select-pkg_class.html b/httemplate/elements/select-pkg_class.html new file mode 100644 index 000000000..0d8e6ac84 --- /dev/null +++ b/httemplate/elements/select-pkg_class.html @@ -0,0 +1,18 @@ +% +% my( $classnum, %opt ) = @_; +% +% $opt{'records'} = delete $opt{'pkg_class'} +% if $opt{'pkg_class'}; +% +% #warn "***** select-pkg-class: \n". Dumper(%opt); +% +% +<% include( '/elements/select-table.html', + 'table' => 'pkg_class', + 'name_col' => 'classname', + 'value' => $classnum, + 'empty_label' => '(none)', + #'hashref' => { 'disabled' => '' }, + %opt, + ) +%> diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html new file mode 100644 index 000000000..83445f41a --- /dev/null +++ b/httemplate/elements/select-table.html @@ -0,0 +1,65 @@ +% +% +% ##required +% # 'table' => 'table_name', +% # 'name_col' => 'name_column', +% # +% ##strongly recommended (you want your forms to be "sticky" on errors, right?) +% # 'value' => 'current_value', +% # +% ##opt +% # 'empty_label' => '', #better specify it though, the default might change +% # 'hashref' => {}, +% # 'extra_sql' => '', +% # 'records' => \@records, #instead of hashref +% # 'pre_options' => [ 'value' => 'option' ], #before normal options +% # 'element_name' => '', #HTML element name, defaults to the name of +% # # the primary key column +% # 'element_etc' => '', #additional attributes (i.e. "DISABLED") for the +% # #<SELECT> element +% +% my( %opt ) = @_; +% +% #warn "***** select-table: \n". Dumper(%opt); +% +% my $key = dbdef->table($opt{'table'})->primary_key; #? $opt{'primary_key'} || +% +% my $name_col = $opt{'name_col'}; +% +% my @records = (); +% if ( $opt{'records'} ) { +% @records = @{ $opt{'records'} }; +% } else { +% @records = qsearch( { +% 'table' => $opt{'table'}, +% 'hashref' => ( $opt{'hashref'} || {} ), +% 'extra_sql' => ( $opt{'extra_sql'} || '' ), +% }); +% } +% +% my @pre_options = $opt{'pre_options'} ? @{ $opt{'pre_options'} } : (); +% +% + + +<SELECT NAME="<% $opt{'element_name'} || $key %>" <% $opt{'element_etc'} %>> +% while ( @pre_options ) { + + <OPTION VALUE="<% shift(@pre_options) %>"><% shift(@pre_options) %> +% } + + + <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %> +% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col() } +% @records +% ) +% { +% + + + <OPTION VALUE="<% $record->$key() %>"<% $opt{'value'} == $record->$key() ? ' SELECTED' : '' %>><% $record->$name_col() %> +% } + + +</SELECT> + diff --git a/httemplate/elements/select-taxclass.html b/httemplate/elements/select-taxclass.html new file mode 100644 index 000000000..495572323 --- /dev/null +++ b/httemplate/elements/select-taxclass.html @@ -0,0 +1,40 @@ +% +% my $conf = new FS::Conf; +% my $selected_taxclass = scalar(@_) ? shift : ''; +% +% if ( $conf->exists('enable_taxclasses') ) { + + + <SELECT NAME="taxclass"> +% if ( $conf->exists('require_taxclasses') ) { + + + <OPTION VALUE="(select)">Select tax class +% } else { + + + <OPTION VALUE=""> +% } +% +% my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county') +% or die dbh->errstr; +% $sth->execute or die $sth->errstr; +% my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref}; +% my @taxclasses = grep $_, keys %taxclasses; +% +% foreach my $taxclass ( @taxclasses ) { + + + <OPTION VALUE="<% $taxclass %>"<% $taxclass eq $selected_taxclass ? ' SELECTED' : '' %>><% $taxclass %> +% } + + + </SELECT> +% } else { + + + <INPUT TYPE="hidden" NAME="taxclass" VALUE="<% $selected_taxclass %>"> +% } + + + diff --git a/httemplate/elements/small_custview.html b/httemplate/elements/small_custview.html new file mode 100644 index 000000000..9060d897d --- /dev/null +++ b/httemplate/elements/small_custview.html @@ -0,0 +1,3 @@ +% my $conf = new FS::Conf; + +<% small_custview( shift, shift || scalar($conf->config('countrydefault')), @_ ) %> diff --git a/httemplate/elements/table-grid.html b/httemplate/elements/table-grid.html new file mode 100644 index 000000000..0f532e86b --- /dev/null +++ b/httemplate/elements/table-grid.html @@ -0,0 +1,21 @@ +% +% my %opt = @_; +% $opt{cellspacing} ||= 0; +% $opt{cellpadding} ||= 0; +% +% + +<STYLE TYPE="text/css"> + +.grid table { border: solid; empty-cells: show } +.grid TH { padding-left: 3px; padding-right: 3px; border: 1px solid #dddddd; border-bottom: dashed 1px black; border-right: none } +.grid TD { padding-left: 3px; padding-right: 3px; empty-cells: show; border: 1px solid #cccccc; border-bottom: none; border-right: none } + +.inv table { border: none } +.inv TH { border: none } +.inv TD { border: none } + +</STYLE> + +<TABLE CLASS="grid" CELLSPACING=<% $opt{cellspacing} %> CELLPADDING=<% $opt{cellpadding} %> BORDER=1 BORDERCOLOR="#000000" STYLE="border: solid 1px black; empty-cells: show"> + diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html new file mode 100644 index 000000000..8152b65d8 --- /dev/null +++ b/httemplate/elements/table.html @@ -0,0 +1,11 @@ +% +% 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/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html new file mode 100644 index 000000000..a8ab6ed10 --- /dev/null +++ b/httemplate/elements/tr-input-beginning_ending.html @@ -0,0 +1,53 @@ +<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> + + +<TR> + <TD ALIGN="right">From: </TD> + <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y<% $time_hint %></i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "beginning_text", + ifFormat: "%m/%d/%Y<% $time_format %>", + button: "beginning_button", + align: "BR" + <% $input_time %> + }); +</SCRIPT> +</TR> + +<TR> + <TD ALIGN="right">To: </TD> + <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y<% $time_hint %></i></TD> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "ending_text", + ifFormat: "%m/%d/%Y<% $time_format %>", + button: "ending_button", + align: "BR" + <% $input_time %> + }); +</SCRIPT> +</TR> + +<TR> + <TD></TD> + <TD> + <FONT SIZE="-1">(leave one or both dates blank for an open-ended search)</FONT> + </TD> +</TR> + +<%init> +my %opt = @_; +my( $input_time, $time_format, $time_hint ) = ( '', '', '' ); +my( $size, $maxlength ) = ( 11, 10 ); +if ( $opt{'input_time'} ) { + $input_time = ', showsTime: true, timeFormat: "12"'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3 + $time_format = ' %k:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5 + $time_hint = ' h:m:s'; + $size = 21; + $maxlength = 27; +} +</%init> diff --git a/httemplate/elements/tr-input-date-field.html b/httemplate/elements/tr-input-date-field.html new file mode 100644 index 000000000..eb8eee450 --- /dev/null +++ b/httemplate/elements/tr-input-date-field.html @@ -0,0 +1,32 @@ + +<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> + +<TR> + <TD ALIGN="right"><% $label %></TD> + <TD> + <INPUT TYPE="text" NAME="<% $name %>" ID="<% $name %>_text" VALUE="<% time2str($format, $value) %>"> + <IMG SRC="../images/calendar.png" ID="<% $name %>_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> +</TR> + +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "<% $name %>_text", + ifFormat: "<% $format %>", + button: "<% $name %>_button", + align: "BR" + }); +</SCRIPT> + + +<%init> +my($name, $value, $label, $format) = @_; + +$format = "%m/%d/%Y" unless $format; +$label = $name unless $label; + +</%init> + diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html new file mode 100644 index 000000000..e443ad26a --- /dev/null +++ b/httemplate/elements/tr-select-access_group.html @@ -0,0 +1,22 @@ +% +% my( $groupnum, %opt ) = @_; +% +% $opt{'access_group'} ||= [ qsearch( 'access_group', {} ) ]; # { disabled=>'' } ) +% +% #warn "***** tr-select-access_group: \n". Dumper(%opt); +% +% if ( scalar(@{ $opt{'access_group'} }) == 0 ) { + + + <INPUT TYPE="hidden" NAME="groupnum" VALUE=""> +% } else { + + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Access group' %></TD> + <TD> + <% include( '/elements/select-access_group.html', $groupnum, %opt ) %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html new file mode 100644 index 000000000..37b1c1e88 --- /dev/null +++ b/httemplate/elements/tr-select-agent.html @@ -0,0 +1,34 @@ +% +% my( $agentnum, %opt ) = @_; +% +% my @agents; +% if ( $opt{'agents'} ) { +% #@agents = @{ $opt{'agents'} }; +% #here is the agent virtualization +% my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href; +% @agents = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agents'} }; +% delete $opt{'agents'}; +% } else { +% @agents = $FS::CurrentUser::CurrentUser->agents; +% } +% +% +% if ( scalar(@agents) == 1 ) { + + + <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agents[0]->agentnum %>"> +% } else { + + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Agent' %></TD> + <TD> + <% include( '/elements/select-agent.html', $agentnum, + 'agents' => \@agents, + %opt, + ) + %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/tr-select-cust-fields.html b/httemplate/elements/tr-select-cust-fields.html new file mode 100644 index 000000000..80562fe3d --- /dev/null +++ b/httemplate/elements/tr-select-cust-fields.html @@ -0,0 +1,15 @@ +% +% my( $cust_fields, %opt ) = @_; +% +% use FS::ConfDefaults; +% $opt{'avail_fields'} ||= [ FS::ConfDefaults->cust_fields_avail() ]; +% +% + + +<TR> + <TD ALIGN="right"><% $opt{'label'} || 'Customer fields' %></TD> + <TD> + <% include( '/elements/select-cust-fields.html', $cust_fields, %opt ) %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-cust_pkg-status.html b/httemplate/elements/tr-select-cust_pkg-status.html new file mode 100644 index 000000000..22ee146cd --- /dev/null +++ b/httemplate/elements/tr-select-cust_pkg-status.html @@ -0,0 +1,14 @@ +% +% my( $status, %opt ) = @_; +% +% $opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } ) +% +% + + +<TR> + <TD ALIGN="right"><% $opt{'label'} || 'Status' %></TD> + <TD> + <% include( '/elements/select-cust_pkg-status.html', $status, %opt ) %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html new file mode 100644 index 000000000..083243d40 --- /dev/null +++ b/httemplate/elements/tr-select-from_to.html @@ -0,0 +1,52 @@ +% +% +% #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); +% my ($curmon,$curyear) = (localtime(time))[4,5]; +% +% #find first month +% my $syear = 1899+$curyear; +% my $smonth = $curmon+1; +% +% #want 12 month by default, not 13 +% $smonth++; +% if ( $smonth > 12 ) { $smonth-=12; $syear++ } +% +% #find last month +% my $eyear = 1900+$curyear; +% my $emonth = $curmon+1; +% +% my %hash = ( +% 'show_month_abbr' => 1, +% 'start_year' => '1999', +% 'end_year' => '2012', #haha, well... +% @_, +% ); +% +% + + +<TR> + <TD ALIGN="right">From: </TD> + <TD> + <% include('/elements/select-month_year.html', + 'prefix' => 'start', + 'selected_mon' => $smonth, + 'selected_year' => $syear, + %hash, + ) + %> + </TD> +</TR> + +<TR> + <TD ALIGN="right">To: </TD> + <TD> + <% include('/elements/select-month_year.html', + 'prefix' => 'end', + 'selected_mon' => $emonth, + 'selected_year' => $eyear, + %hash, + ) + %> + </TD> +</TR> diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html new file mode 100644 index 000000000..35c5b8047 --- /dev/null +++ b/httemplate/elements/tr-select-part_referral.html @@ -0,0 +1,30 @@ +% +% my( $refnum, %opt ) = @_; +% +% $opt{'part_referrals'} ||= +% [ FS::part_referral->all_part_referral( 1 ) ]; #1: include global +% +% my $r = qq!<font color="#ff0000">*</font> !; +% +% +% if ( scalar( @{$opt{'part_referrals'}} ) == 0 ) { +% eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.html and create one or more advertising sources."; +% } elsif ( scalar( @{$opt{'part_referrals'}} ) == 1 ) { +% + + + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $opt{'part_referrals'}->[0]->refnum %>"> +% } else { + + + <TR> + <TH ALIGN="right"><%$r%>Advertising source</TH> + <TD> + <% include( '/elements/select-part_referral.html', $refnum, + 'part_referrals' => $opt{'part_referrals'}, + ) + %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/tr-select-pkg_class.html b/httemplate/elements/tr-select-pkg_class.html new file mode 100644 index 000000000..7f37e816e --- /dev/null +++ b/httemplate/elements/tr-select-pkg_class.html @@ -0,0 +1,22 @@ +% +% my( $classnum, %opt ) = @_; +% +% $opt{'pkg_class'} ||= [ qsearch( 'pkg_class', {} ) ]; # { disabled=>'' } ) +% +% #warn "***** tr-select-pkg-class: \n". Dumper(%opt); +% +% if ( scalar(@{ $opt{'pkg_class'} }) == 0 ) { + + + <INPUT TYPE="hidden" NAME="classnum" VALUE=""> +% } else { + + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Package class' %></TD> + <TD> + <% include( '/elements/select-pkg_class.html', $classnum, %opt ) %> + </TD> + </TR> +% } + diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css new file mode 100644 index 000000000..60881b813 --- /dev/null +++ b/httemplate/elements/xmenu.css @@ -0,0 +1,195 @@ + +.webfx-menu, .webfx-menu * { + /* + Set the box sizing to content box + in the future when IE6 supports box-sizing + there will be an issue to fix the sizes + + There is probably an issue with IE5 mac now + because IE5 uses content-box but the script + assumes all versions of IE uses border-box. + + At the time of this writing mozilla did not support + box-sizing for absolute positioned element. + + Opera only supports content-box + */ + box-sizing: content-box; + -moz-box-sizing: content-box; +} + +.webfx-menu { + position: absolute; + z-index: 100; + visibility: hidden; + width: 154px; + border: 1px solid black; + padding: 1px; + background: white; + filter: progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4) + alpha(Opacity=95); + -moz-opacity: 0.95; + /* a drop shadow would be nice in moz/others too... */ +} + +.webfx-menu-empty { + display: block; + border: 1px solid white; + padding: 2px 5px 2px 5px; + font-size: 11px; + /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */ + color: black; +} + +.webfx-menu a { + display: block; + width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */ + height: expression(constExpression("1px")); + overflow: visible; + padding: 2px 0px 2px 5px; + font-size: 11px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + vertical-align: center; + color: black; + border: 1px solid white; +} + +.webfx-menu a:visited { + color: black; + border: 1px solid white; +} + +.webfx-menu a:hover { + color: black; + border: 1px solid #7e0079; +} + +.webfx-menu a:hover { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + /* background: #ffe6fe; */ + /* background: #ffc2fe; */ + background: #fff2fe; + border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ +} + +.webfx-menu a .arrow { + float: right; + border: 0; + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +/* separtor */ +.webfx-menu div { + height: 0; + height: expression(constExpression(ieBox ? "2px" : "0")); + border-top: 1px solid #7e0079; /* rgb(120,172,255); */ + border-bottom: 1px solid rgb(234,242,255); + overflow: hidden; + margin: 2px 0px 2px 0px; + font-size: 0mm; +} + +.webfx-menu-bar { + /* i want a vertical bar */ + display: block; + + /* background: rgb(120,172,255);/*rgb(255,128,0);*/ + /* background: #a097ed; */ + background: #000000; + /* border: 1px solid #7E0079; */ + /* border: 1px solid #000000; */ + /* border: none */ + color: white; + + padding: 2px; + + font-family: Verdana, Helvetica, Sans-Serif; + /* font-size: 11px; */ + + /* IE5.0 has the wierdest box model for inline elements */ + padding: expression(constExpression(ie50 ? "0px" : "2px")); +} + +.webfx-menu-bar a, +.webfx-menu-bar a:visited { + /* i want a vertical bar */ + display: block; + + /* border: 1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/ + /* border: 1px solid black; /* #ffffff; */ + /* border-bottom: 1px solid black; */ + border-bottom: 1px solid white; + /* border-bottom: 1px solid rgb(0,66,174); + /* border-bottom: 1px solid black; + border-bottom: 1px solid black; + border-bottom: 1px solid black; */ + + padding: 1px 5px 1px 5px; + + /* color: black; */ + color: white; + text-decoration: none; + + /* IE5.0 Does not paint borders and padding on inline elements without a height/width */ + height: expression(constExpression(ie50 ? "17px" : "auto")); +} + +.webfx-menu-bar a:link { + color: white; +} + +.webfx-menu-bar a:hover { + /* color: black; */ + color: white; + /* background: rgb(120,172,255); */ + /* background: #BC79B8; */ + background: #7E0079; + /* border-left: 1px solid rgb(234,242,255); + border-right: 1px solid rgb(0,66,174); + border-top: 1px solid rgb(234,242,255); + border-bottom: 1px solid rgb(0,66,174); */ +} + +.webfx-menu-bar a .arrow { + border: 0; + float: right; +/* vertical-align: top; */ + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +.webfx-menu-bar a:active, .webfx-menu-bar a:focus { + -moz-outline: none; + outline: none; + /* + ie does not support outline but ie55 can hide the outline using + a proprietary property on HTMLElement. Did I say that IE sucks at CSS? + */ + ie-dummy: expression(this.hideFocus=true); + + border-left: 1px solid rgb(0,66,174); + border-right: 1px solid rgb(234,242,255); + border-top: 1px solid rgb(0,66,174); + border-bottom: 1px solid rgb(234,242,255); +} + +.webfx-menu-title { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + background: #7e0079; +/* border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ + padding: 3px 1px 3px 6px; + display: block; + font-size: 13px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + color: white; +/* border: 1px solid white; */ + border-bottom: 1px solid white; +} + diff --git a/httemplate/elements/xmenu.js b/httemplate/elements/xmenu.js new file mode 100644 index 000000000..134265f53 --- /dev/null +++ b/httemplate/elements/xmenu.js @@ -0,0 +1,668 @@ +//<script> +/* + * This script was created by Erik Arvidsson (erik@eae.net) + * for WebFX (http://webfx.eae.net) + * Copyright 2001 + * + * For usage see license at http://webfx.eae.net/license.html + * + * Created: 2001-01-12 + * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks + * 2001-12-20 Added auto positioning and some properties to support this + * 2002-08-13 toString used ' for attributes. Changed to " to allow in args + */ + +// check browsers +var ua = navigator.userAgent; +var opera = /opera [56789]|opera\/[56789]/i.test(ua); +var ie = !opera && /MSIE/.test(ua); +var ie50 = ie && /MSIE 5\.[01234]/.test(ua); +var ie6 = ie && /MSIE [6789]/.test(ua); +var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat"); +var moz = !opera && /gecko/i.test(ua); +var nn6 = !opera && /netscape.*6\./i.test(ua); +var khtml = /KHTML/i.test(ua); + +// define the default values + +webfxMenuDefaultWidth = 154; + +webfxMenuDefaultBorderLeft = 1; +webfxMenuDefaultBorderRight = 1; +webfxMenuDefaultBorderTop = 1; +webfxMenuDefaultBorderBottom = 1; + +webfxMenuDefaultPaddingLeft = 1; +webfxMenuDefaultPaddingRight = 1; +webfxMenuDefaultPaddingTop = 1; +webfxMenuDefaultPaddingBottom = 1; + +webfxMenuDefaultShadowLeft = 0; +webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0; +webfxMenuDefaultShadowTop = 0; +webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0; + + +webfxMenuItemDefaultHeight = 18; +webfxMenuItemDefaultText = "Untitled"; +webfxMenuItemDefaultHref = "javascript:void(0)"; + +webfxMenuSeparatorDefaultHeight = 6; + +webfxMenuDefaultEmptyText = "Empty"; + +webfxMenuDefaultUseAutoPosition = nn6 ? false : true; + + + +// other global constants + +webfxMenuImagePath = ""; + +webfxMenuUseHover = opera ? true : false; +webfxMenuHideTime = 500; +webfxMenuShowTime = 200; + + + +var webFXMenuHandler = { + idCounter : 0, + idPrefix : "webfx-menu-object-", + all : {}, + getId : function () { return this.idPrefix + this.idCounter++; }, + overMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuShowTime <= 0) + this._over(jsItem); + else if ( jsItem ) + //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime); + // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object + this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime); + }, + outMenuItem : function (oItem) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + var jsItem = this.all[oItem.id]; + if (webfxMenuHideTime <= 0) + this._out(jsItem); + else if ( jsItem ) + //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime); + this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime); + }, + blurMenu : function (oMenuItem) { + window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime); + }, + _over : function (jsItem) { + if (jsItem.subMenu) { + jsItem.parentMenu.hideAllSubs(); + jsItem.subMenu.show(); + } + else + jsItem.parentMenu.hideAllSubs(); + }, + _out : function (jsItem) { + // find top most menu + var root = jsItem; + var m; + if (root instanceof WebFXMenuButton) + m = root.subMenu; + else { + m = jsItem.parentMenu; + while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar)) + m = m.parentMenu; + } + if (m != null) + m.hide(); + }, + hideMenu : function (menu) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime); + }, + showMenu : function (menu, src, dir) { + if (this.showTimeout != null) + window.clearTimeout(this.showTimeout); + if (this.hideTimeout != null) + window.clearTimeout(this.hideTimeout); + + if (arguments.length < 3) + dir = "vertical"; + + menu.show(src, dir); + } +}; + +function WebFXMenu() { + this._menuItems = []; + this._subMenus = []; + this.id = webFXMenuHandler.getId(); + this.top = 0; + this.left = 0; + this.shown = false; + this.parentMenu = null; + webFXMenuHandler.all[this.id] = this; +} + +WebFXMenu.prototype.width = webfxMenuDefaultWidth; +WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText; +WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition; + +WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft; +WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight; +WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop; +WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom; + +WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft; +WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight; +WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop; +WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom; + +WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft; +WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight; +WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop; +WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom; + + + +WebFXMenu.prototype.add = function (menuItem) { + this._menuItems[this._menuItems.length] = menuItem; + if (menuItem.subMenu) { + this._subMenus[this._subMenus.length] = menuItem.subMenu; + menuItem.subMenu.parentMenu = this; + } + + menuItem.parentMenu = this; +}; + +WebFXMenu.prototype.show = function (relObj, sDir) { + if (this.useAutoPosition) + this.position(relObj, sDir); + + var divElement = document.getElementById(this.id); + if ( divElement ) { + + divElement.style.left = opera ? this.left : this.left + "px"; + divElement.style.top = opera ? this.top : this.top + "px"; + divElement.style.visibility = "visible"; + + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.width = divElement.offsetWidth; + shimElement.style.height = divElement.offsetHeight; + shimElement.style.top = divElement.style.top; + shimElement.style.left = divElement.style.left; + /*shimElement.style.zIndex = divElement.style.zIndex - 1; */ + shimElement.style.display = "block"; + shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; + } + } + + } + + this.shown = true; + + if (this.parentMenu) + this.parentMenu.show(); +}; + +WebFXMenu.prototype.hide = function () { + this.hideAllSubs(); + var divElement = document.getElementById(this.id); + if ( divElement ) { + divElement.style.visibility = "hidden"; + if ( ie ) { + var shimElement = document.getElementById(this.id + "Shim"); + if ( shimElement ) { + shimElement.style.display = "none"; + } + } + } + + this.shown = false; +}; + +WebFXMenu.prototype.hideAllSubs = function () { + for (var i = 0; i < this._subMenus.length; i++) { + if (this._subMenus[i].shown) + this._subMenus[i].hide(); + } +}; + +WebFXMenu.prototype.toString = function () { + var top = this.top + this.borderTop + this.paddingTop; + var str = "<div id='" + this.id + "' class='webfx-menu' style='" + + "width:" + (!ieBox ? + this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight : + this.width) + "px;" + + (this.useAutoPosition ? + "left:" + this.left + "px;" + "top:" + this.top + "px;" : + "") + + (ie50 ? "filter: none;" : "") + + "'>"; + + if (this._menuItems.length == 0) { + str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>"; + } + else { + str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + '>' + this.emptyText + '</span>'; + // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>'; + // loop through all menuItems + for (var i = 0; i < this._menuItems.length; i++) { + var mi = this._menuItems[i]; + str += mi; + if (!this.useAutoPosition) { + if (mi.subMenu && !mi.subMenu.useAutoPosition) + mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop; + top += mi.height; + } + } + + } + + str += "</div>"; + + if ( ie ) { + str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>"; + } + + for (var i = 0; i < this._subMenus.length; i++) { + this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft; + str += this._subMenus[i]; + } + + return str; +}; +// WebFXMenu.prototype.position defined later + +function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) { + this.text = sText || webfxMenuItemDefaultText; + this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref; + this.subMenu = oSubMenu; + if (oSubMenu) + oSubMenu.parentMenuItem = this; + this.toolTip = sToolTip; + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight; +WebFXMenuItem.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href=\"" + this.href + "\"" + + (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") + + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") + + (this.subMenu ? " unselectable='on' tabindex='-1'" : "") + + ">" + + (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") + + this.text + + "</a>"; +}; + + +function WebFXMenuSeparator() { + this.id = webFXMenuHandler.getId(); + webFXMenuHandler.all[this.id] = this; +}; +WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight; +WebFXMenuSeparator.prototype.toString = function () { + return "<div" + + " id='" + this.id + "'" + + (webfxMenuUseHover ? + " onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'" + : + "") + + "></div>" +}; + +function WebFXMenuBar() { + this._parentConstructor = WebFXMenu; + this._parentConstructor(); +} +WebFXMenuBar.prototype = new WebFXMenu; +WebFXMenuBar.prototype.toString = function () { + var str = "<div id='" + this.id + "' class='webfx-menu-bar'>"; + + // loop through all menuButtons + for (var i = 0; i < this._menuItems.length; i++) + str += this._menuItems[i]; + + str += "</div>"; + + for (var i = 0; i < this._subMenus.length; i++) + str += this._subMenus[i]; + + return str; +}; + +function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) { + this._parentConstructor = WebFXMenuItem; + this._parentConstructor(sText, sHref, sToolTip, oSubMenu); +} +WebFXMenuButton.prototype = new WebFXMenuItem; +WebFXMenuButton.prototype.toString = function () { + return "<a" + + " id='" + this.id + "'" + + " href='" + this.href + "'" + + (this.toolTip ? " title='" + this.toolTip + "'" : "") + + (webfxMenuUseHover ? + (" onmouseover='webFXMenuHandler.overMenuItem(this)'" + + " onmouseout='webFXMenuHandler.outMenuItem(this)'") : + ( + " onfocus='webFXMenuHandler.overMenuItem(this)'" + + (this.subMenu ? + " onblur='webFXMenuHandler.blurMenu(this)'" : + "" + ) + )) + + ">" + + (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.right.png'>" : "") + + this.text + + "</a>"; +}; + + + + + +/* Position functions */ + + +function getInnerLeft(el) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) ); + +} + + + +function getLeft(el, debug) { + + if (el == null) return 0; + + //if ( debug ) + // alert ( el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) ); + + return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) ); + +} + + + +function getInnerTop(el) { + + if (el == null) return 0; + + if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0; + + return parseInt( getTop(el) + parseInt(getBorderTop(el)) ); + +} + + + +function getTop(el) { + + if (el == null) return 0; + + return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) ); + +} + + + +function getBorderLeft(el) { + + return ie ? + + el.clientLeft : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width")) + ); + +} + + + +function getBorderTop(el) { + + return ie ? + + el.clientTop : + + ( khtml + ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width")) + : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width")) + ); + +} + + + +function opera_getLeft(el) { + + if (el == null) return 0; + + return el.offsetLeft + opera_getLeft(el.offsetParent); + +} + + + +function opera_getTop(el) { + + if (el == null) return 0; + + return el.offsetTop + opera_getTop(el.offsetParent); + +} + + + +function getOuterRect(el, debug) { + + return { + + left: (opera ? opera_getLeft(el) : getLeft(el, debug)), + + top: (opera ? opera_getTop(el) : getTop(el)), + + width: el.offsetWidth, + + height: el.offsetHeight + + }; + +} + + + +// mozilla bug! scrollbars not included in innerWidth/height + +function getDocumentRect(el) { + + return { + + left: 0, + + top: 0, + + width: (ie ? + + (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) : + + window.innerWidth + + ), + + height: (ie ? + + (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) : + + window.innerHeight + + ) + + }; + +} + + + +function getScrollPos(el) { + + return { + + left: (ie ? + + (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) : + + window.pageXOffset + + ), + + top: (ie ? + + (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) : + + window.pageYOffset + + ) + + }; + +} + + +/* end position functions */ + +WebFXMenu.prototype.position = function (relEl, sDir) { + var dir = sDir; + // find parent item rectangle, piRect + var piRect; + if (!relEl) { + var pi = this.parentMenuItem; + if (!this.parentMenuItem) + return; + + relEl = document.getElementById(pi.id); + if (dir == null) + dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal"; + //alert('created RelEl from parent: ' + pi.id); + piRect = getOuterRect(relEl, 1); + } + else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect + //alert('passed a Rect as RelEl: ' + typeof(relEl)); + + piRect = relEl; + } + else { + //alert('passed an element as RelEl: ' + typeof(relEl)); + piRect = getOuterRect(relEl); + } + + var menuEl = document.getElementById(this.id); + var menuRect = getOuterRect(menuEl); + var docRect = getDocumentRect(); + var scrollPos = getScrollPos(); + var pMenu = this.parentMenu; + + if (dir == "vertical") { + if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) { + //alert('piRect.left: ' + piRect.left); + this.left = piRect.left; + if ( ! ie ) + this.left = this.left + 138; + } else if (docRect.width >= menuRect.width) { + //konq (not safari though) winds up here by accident and positions the menus all weird + //alert('docRect.width + scrollPos.left - menuRect.width'); + + this.left = docRect.width + scrollPos.left - menuRect.width; + } else { + //alert('scrollPos.left: ' + scrollPos.left); + this.left = scrollPos.left; + } + + if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top) + + this.top = piRect.top + piRect.height; + + else if (piRect.top - menuRect.height >= scrollPos.top) + + this.top = piRect.top - menuRect.height; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + } + else { + if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top) + + this.top = piRect.top - this.borderTop - this.paddingTop; + + else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0) + + this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom; + + else if (docRect.height >= menuRect.height) + + this.top = docRect.height + scrollPos.top - menuRect.height; + + else + + this.top = scrollPos.top; + + + + var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0; + + var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0; + + var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0; + + var pMenuBorderRight = pMenu ? pMenu.borderRight : 0; + + + + if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight + + + pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left) + + this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft; + + else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0) + + this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight; + + else if (docRect.width >= menuRect.width) + + this.left = docRect.width + scrollPos.left - menuRect.width; + + else + + this.left = scrollPos.left; + } +}; diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html new file mode 100644 index 000000000..6efc395f7 --- /dev/null +++ b/httemplate/elements/xmlhttp.html @@ -0,0 +1,111 @@ +% +% my ( %opt ) = @_; +% +% my $url = $opt{'url'}; +% my $method = exists($opt{'method'}) ? $opt{'method'} : 'GET'; +% #my @subs = @{ $opt{'subs'}; +% my $key = exists($opt{'key'}) ? $opt{'key'} : ''; +% +% $url .= ( ($url =~ /\?/) ? '&' : '?' ) +% if $method eq 'GET'; +% +% + + +<SCRIPT TYPE="text/javascript"> + + function rs_init_object() { + var A; + try { + A=new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + A=new ActiveXObject("Microsoft.XMLHTTP"); + } catch (oc) { + A=null; + } + } + if(!A && typeof XMLHttpRequest != "undefined") + A = new XMLHttpRequest(); + if (!A) + alert("Can't create XMLHttpRequest object"); + return A; + + } +% foreach my $func ( @{$opt{'subs'}} ) { +% +% my $furl = $url; +% $furl =~ s/\"/\\\\\"/; #javascript escape +% +% + + + function <%$key%><%$func%>() { + // count args; build URL + var url = "<%$furl%>"; + var a = <%$key%><%$func%>.arguments; + + var args; + var len; + var content = 'sub=<% uri_escape($func) %>'; + if ( a && typeof a == 'object' && a[0].constructor == Array ) { + args = a[0]; + len = args.length + } else { + args = a; + len = args.length - 1; + } + for (var i = 0; i < len; i++) + content = content + "&arg=" + escape(args[i]); + content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs + + if ( '<%$method%>' == 'GET' ) { + url = url + content; + } + + //alert('<%$method%> ' + url); + + var xmlhttp = rs_init_object(); + xmlhttp.open("<%$method%>", url, true); + + xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState != 4) + return; + + if (xmlhttp.status != 200) { + alert(xmlhttp.status + " status connecting to " + url); + } else { + var data = xmlhttp.responseText; + //alert('received response: ' + data); + a[a.length-1](data); + if ( data.indexOf("<b>System error</b>") > -1 ) { + var w; + if ( w = window.open("about:blank") ) { + w.document.write(data); + } else { + // popup blocking? should use an overlib popup instead + alert("Error popup disabled; try disabling popup blocking to see"); + } + } + } + } + + if ( '<%$method%>' == 'POST' ) { + + xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xmlhttp.send(content); + + } else { + + xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); + xmlhttp.send(null); + + } + + //rs_debug("x_$func_name url = " + url); + //rs_debug("x_$func_name waiting.."); + } +% } + + +</SCRIPT> diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi new file mode 100644 index 000000000..ea5ae0b2b --- /dev/null +++ b/httemplate/graph/cust_bill_pkg.cgi @@ -0,0 +1,119 @@ +% +% +% +%#find first month +%my $syear = $cgi->param('start_year'); # || 1899+$curyear; +%my $smonth = $cgi->param('start_month'); # || $curmon+1; +% +%#find last month +%my $eyear = $cgi->param('end_year'); # || 1900+$curyear; +%my $emonth = $cgi->param('end_month'); # || $curmon+1; +% +%#XXX or virtual +%my( $agentnum, $sel_agent ) = ('', ''); +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agentnum = $1; +% $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% die "agentnum $agentnum not found!" unless $sel_agent; +%} +%my $title = $sel_agent ? $sel_agent->agent.' ' : ''; +% +%#false lazinessish w/search/cust_pkg.cgi +%my $classnum = 0; +%my @pkg_class = (); +%if ( $cgi->param('classnum') =~ /^(\d*)$/ ) { +% $classnum = $1; +% if ( $classnum ) { +% @pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); +% die "classnum $classnum not found!" unless $pkg_class[0]; +% $title .= $pkg_class[0]->classname.' '; +% } elsif ( $classnum eq '' ) { +% $title .= 'Empty class '; +% @pkg_class = ( '(empty class)' ); +% } elsif ( $classnum eq '0' ) { +% @pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); +% push @pkg_class, '(empty class)'; +% } +%} +%#eslaf +% +%my $hue = 0; +%#my $hue_increment = 170; +%#my $hue_increment = 145; +%my $hue_increment = 125; +% +%my @items = (); +%my @params = (); +%my @labels = (); +%my @colors = (); +%my @links = (); +% +%my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1"; +% +%foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) { +% +% my $col_scheme = Color::Scheme->new +% ->from_hue($hue) #->from_hex($agent->color) +% ->scheme('analogic') +% ; +% my @recur_colors = (); +% my @onetime_colors = (); +% +% ### fixup the color handling for package classes... +% my $n = 0; +% +% foreach my $pkg_class ( @pkg_class ) { +% +% push @items, 'cust_bill_pkg'; +% +% +% push @labels, +% ( $sel_agent ? '' : $agent->agent.' ' ). +% ( $classnum eq '0' +% ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class ) +% : '' +% ); +% +% my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0; +% my $row_agentnum = $agent->agentnum; +% push @params, [ 'classnum' => $row_classnum, +% 'agentnum' => $row_agentnum, +% ]; +% +% push @links, "$link;agentnum=$row_agentnum;classnum=$row_classnum;"; +% +% @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9] +% unless @recur_colors; +% @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11] +% unless @onetime_colors; +% push @colors, shift @recur_colors; +% +% } +% +% $hue += $hue_increment; +% +%} +% +%#use Data::Dumper; +%#warn Dumper(\@items); +% +% +<% include('elements/monthly.html', + 'title' => $title. 'Sales Report (Gross)', + 'graph_type' => 'Mountain', + 'items' => \@items, + 'params' => \@params, + 'labels' => \@labels, + 'graph_labels' => \@labels, + 'colors' => \@colors, + 'links' => \@links, + 'remove_empty' => 1, + 'bottom_total' => 1, + 'bottom_link' => "$link;", + 'start_month' => $smonth, + 'start_year' => $syear, + 'end_month' => $emonth, + 'end_year' => $eyear, + 'agentnum' => $agentnum, + ) +%> diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html new file mode 100644 index 000000000..f5789a2a2 --- /dev/null +++ b/httemplate/graph/elements/monthly.html @@ -0,0 +1,207 @@ +% +% +% # options example... +% # +% # 'title' => 'Page title', +% # 'items' => \@items, +% # 'params' => \@params, # opt, +% # 'labels' => \@labels, # or \%labels (keys are items) +% # 'graph_labels' => \@graph_labels, # or \%graph_labels, +% # 'colors' => \@colors, # or \%colors, +% # 'links => \@links, # or \%link, #opt +% # 'start_month' => $smonth, +% # 'start_year' => $syear, +% # 'end_month' => $emonth, +% # 'end_year' => $eyear, +% # 'agentnum' => $agentnum, #opt +% # 'nototal' => 1, #opt, +% # 'graph_type' => 'LinesPoints', #opt +% # 'remove_empty' => 1, #opt, +% # 'bottom_total' => 1, #opt, +% +% my(%opt) = @_; +% my @items = @{ $opt{'items'} }; +% +% foreach my $other (qw( labels graph_labels colors links )) { +% #foreach my $other (qw( labels graph_labels colors )) { +% if ( ref($opt{$other}) eq 'HASH' ) { +% $opt{$other} = [ map $opt{$other}{$_}, @items ]; +% } +% } +% +% my $report = new FS::Report::Table::Monthly ( +% +% #'items' => $opt{'items'}, +% 'items' => \@items, +% 'params' => $opt{'params'}, +% 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/ +% ? $opt{'graph_labels'} +% : $opt{'labels'} +% ), +% 'colors' => $opt{'colors'}, +% 'links' => $opt{'links'}, +% +% 'start_month' => $opt{'start_month'}, +% 'start_year' => $opt{'start_year'}, +% 'end_month' => $opt{'end_month'}, +% 'end_year' => $opt{'end_year'}, +% +% 'agentnum' => $opt{'agentnum'}, +% 'remove_empty' => $opt{'remove_empty'}, +% ); +% my $data = $report->data; +% +% if ( $cgi->param('_type') =~ /^(png)$/ ) { +% +% #my $chart = Chart::LinesPoints->new(1024,480); +% #my $chart = Chart::LinesPoints->new(768,480); +% +% my $graph_type = 'LinesPoints'; +% if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain)$/ ) { +% $graph_type = $1; +% } +% my $class = "Chart::$graph_type"; +% +% my $chart = $class->new(976,384); +% +% my $d = 0; +% $chart->set( +% #'min_val' => 0, +% 'legend' => 'bottom', +% 'colors' => { ( +% map { my $color = $_; +% 'dataset'.$d++ => +% [ map hex($_), unpack 'a2a2a2', $color ] +% } +% #@{ $opt{'colors'} } +% @{ $data->{'colors'} } +% ), +% #'grey_background' => [ 211, 211, 211 ], +% 'grey_background' => 'white', +% 'background' => [ 0xe8, 0xe8, 0xe8 ], #grey +% }, +% #'grey_background' => 'false', +% 'legend_labels' => $data->{'item_labels'}, +% 'brush_size' => 4, +% #'pt_size' => 12, +% ); +% +% #my @data = map { $data->{$_} } ( 'label', @items ); +% my @data = @{ $data->{data} }; +% unshift @data, $data->{'label'}; +% +% http_header('Content-Type' => 'image/png' ); +% +% $chart->_set_colors(); +% +% +<% $chart->scalar_png(\@data) %> +% +% +% } else { +% +% my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); +% +% +<% include('/elements/header.html', $opt{'title'} ) %> +% $cgi->param('_type', 'png'); + +<IMG SRC="<% $cgi->self_url %>" WIDTH="976" HEIGHT="384"> +<BR> + +<% table('e8e8e8') %> + +<TR> + + <TD></TD> +% foreach my $column ( @{$data->{label}} ) { +% #$column =~ s/^(\d+)\//$mon[$1-1]<BR>/e; +% $column =~ s/^(\d+)\//$mon[$1-1]<BR>/; +% + + <TH><% $column %></TH> +% } +% unless ( $opt{'nototal'} ) { + + <TH>Total</TH> +% } + + +</TR> +% my @bottom_total = (); +% foreach my $row ( @{ $data->{'items'} } ) { +% +% #my $color = shift( @{ $opt{'colors'} } ); +% my $color = shift( @{ $data->{'colors'} } ); +% my $link = shift( @{ $data->{'links'} } ); +% $link = $link ? qq(<A HREF="$link) : ''; +% + + + <TR> + + <TH><FONT COLOR="#<% $color %>"><% shift( @{ $data->{'item_labels'} } ) %></FONT></TH> +% #my $link = exists($opt{'links'}{$row}) +% # ? qq(<A HREF="$opt{'links'}{$row}) +% # : ''; +% my @speriod = @{$data->{speriod}}; +% my @eperiod = @{$data->{eperiod}}; +% my $total = 0; +% +% my $col = 0; +% foreach my $column ( @{ shift( @{$data->{data}} ) } ) { # ( @{$data->{$row}} ) { +% + + + <TD ALIGN="right" BGCOLOR="#ffffff"> + <% $link ? $link. 'begin='. shift(@speriod). ';end='. shift(@eperiod). '">' : '' %><FONT COLOR="#<% $color %>">$<% sprintf("%.2f", $column) %></FONT><% $link ? '</A>' : '' %> + </TD> +% +% $total += $column; +% $bottom_total[$col++] += $column; +% +% } +% unless ( $opt{'nototal'} ) { + + + <TD ALIGN="right" BGCOLOR="#f5f6be"> + <% $link ? $link. 'begin='. ${$data->{speriod}}[0]. ';end='. ${$data->{eperiod}}[-1]. '">' : '' %><FONT COLOR="#<% $color %>">$<% sprintf("%.2f", $total) %></FONT><% $link ? '</A>' : '' %> + </TD> +% $bottom_total[$col++] += $total; +% } + + + </TR> +% } +% if ( $opt{'bottom_total'} ) { +% my @speriod = ( @{$data->{speriod}}, ${$data->{speriod}}[0] ); +% my @eperiod = ( @{$data->{eperiod}}, ${$data->{eperiod}}[-1] ); +% + + + <TR> + <TH>Total</TH> +% foreach my $total ( @bottom_total ) { + + + <TD ALIGN="right" BGCOLOR="#f5f6be"> + <% $opt{'bottom_link'} + ? '<A HREF="'. $opt{'bottom_link'}. + 'begin='. shift(@speriod). + ';end='. shift(@eperiod). '">' + : '' + %>$<% sprintf("%.2f", $total) %><% $opt{'bottom_link'} ? '</A>' : '' %> + + </TD> +% } + + + </TR> +% } + + +</TABLE> + +<% include('/elements/footer.html') %> +% } + diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi new file mode 100644 index 000000000..829b1e66f --- /dev/null +++ b/httemplate/graph/money_time.cgi @@ -0,0 +1,81 @@ +% +% +%#find first month +%my $syear = $cgi->param('start_year'); # || 1899+$curyear; +%my $smonth = $cgi->param('start_month'); # || $curmon+1; +% +%#find last month +%my $eyear = $cgi->param('end_year'); # || 1900+$curyear; +%my $emonth = $cgi->param('end_month'); # || $curmon+1; +% +%#XXX or virtual +%my( $agentnum, $agent ) = ('', ''); +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agentnum = $1; +% $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% die "agentnum $agentnum not found!" unless $agent; +%} +%my $agentname = $agent ? $agent->agent.' ' : ''; +% +%my @items = qw( invoiced netsales credits payments receipts ); +%if ( $cgi->param('12mo') == 1 ) { +% @items = map $_.'_12mo', @items; +%} +% +%my %label = ( +% 'invoiced' => 'Gross Sales', +% 'netsales' => 'Net Sales', +% 'credits' => 'Credits', +% 'payments' => 'Gross Receipts', +% 'receipts' => 'Net Receipts', +%); +% +%my %graph_suffix = ( +% 'invoiced' => ' (invoiced)', +% 'netsales' => ' (invoiced - applied credits)', +% 'credits' => '', +% 'payments' => ' (payments)', +% 'receipts' => '/Cashflow (payments - refunds)', +%); +%my %graph_label = map { $_ => $label{$_}.$graph_suffix{$_} } keys %label; +% +%$label{$_.'_12mo'} = $label{$_}. " (previous 12 months)" +% foreach keys %label; +% +%$graph_label{$_.'_12mo'} = $graph_label{$_}. " (previous 12 months)" +% foreach keys %graph_label; +% +%my %color = ( +% 'invoiced' => '9999ff', #light blue +% 'netsales' => '0000cc', #blue +% 'credits' => 'cc0000', #red +% 'payments' => '99cc99', #light green +% 'receipts' => '00cc00', #green +%); +%$color{$_.'_12mo'} = $color{$_} +% foreach keys %color; +% +%my %link = ( +% 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;", +% 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", +% 'payments' => "${p}search/cust_pay.cgi?magic=_date;agentnum=$agentnum;", +%); +%# XXX link 12mo? +% +% +<% include('elements/monthly.html', + 'title' => $agentname. + 'Sales, Credits and Receipts Summary', + 'items' => \@items, + 'labels' => \%label, + 'graph_labels' => \%graph_label, + 'colors' => \%color, + 'links' => \%link, + 'start_month' => $smonth, + 'start_year' => $syear, + 'end_month' => $emonth, + 'end_year' => $eyear, + 'agentnum' => $agentnum, + 'nototal' => scalar($cgi->param('12mo')), + ) +%> diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html new file mode 100644 index 000000000..664aab74e --- /dev/null +++ b/httemplate/graph/report_cust_bill_pkg.html @@ -0,0 +1,29 @@ +<% include('/elements/header.html', 'Sales Report' ) %> + +<FORM ACTION="cust_bill_pkg.cgi" METHOD="GET"> + +<TABLE> + +<% include('/elements/tr-select-from_to.html' ) %> + +<% include('/elements/tr-select-agent.html', 'label' => 'For agent: ' ) %> + +<% include('/elements/tr-select-pkg_class.html', '', + 'pre_options' => [ '0' => 'all' ], + 'empty_label' => '(empty class)', + ) +%> + +<!-- +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="separate_0freq" VALUE="1"></TD> + <TD>Separate one-time vs. recurring sales</TD> +</TR> +--> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html new file mode 100644 index 000000000..b80696b65 --- /dev/null +++ b/httemplate/graph/report_money_time.html @@ -0,0 +1,33 @@ +<% include('/elements/header.html', 'Sales, Credits and Receipts Summary' ) %> + +<FORM ACTION="money_time.cgi" METHOD="GET"> + +<!-- +<INPUT TYPE="checkbox" NAME="ar"> + Accounts receivable (invoices - applied credits)<BR> +<INPUT TYPE="checkbox" NAME="charged"> + Just Invoices<BR> +<INPUT TYPE="checkbox" NAME="defer"> + Accounts receivable, with deferred revenue (invoices - applied credits, with charges for annual/semi-annual/quarterly/etc. services deferred over applicable time period) (there has got to be a shorter description for this)<BR> +<INPUT TYPE="checkbox" NAME="cash"> + Cashflow (payments - refunds)<BR> +<BR> +--> + +<TABLE> + +<% include('/elements/tr-select-from_to.html' ) %> + +<% include('/elements/tr-select-agent.html', '', 'label' => 'For agent: ' ) %> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="12mo" VALUE="1"></TD> + <TD>Show 12 month totals instead of monthly values</TD> +</TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/images/32clear.gif b/httemplate/images/32clear.gif Binary files differnew file mode 100644 index 000000000..5fdcea204 --- /dev/null +++ b/httemplate/images/32clear.gif diff --git a/httemplate/images/ach.png b/httemplate/images/ach.png Binary files differnew file mode 100644 index 000000000..fdcd5e6ed --- /dev/null +++ b/httemplate/images/ach.png diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png Binary files differnew file mode 100644 index 000000000..675d84bde --- /dev/null +++ b/httemplate/images/arrow.down.png diff --git a/httemplate/images/arrow.right.black.png b/httemplate/images/arrow.right.black.png Binary files differnew file mode 100644 index 000000000..933c25894 --- /dev/null +++ b/httemplate/images/arrow.right.black.png diff --git a/httemplate/images/arrow.right.png b/httemplate/images/arrow.right.png Binary files differnew file mode 100644 index 000000000..60bcb76ab --- /dev/null +++ b/httemplate/images/arrow.right.png diff --git a/httemplate/images/background-cheat.png b/httemplate/images/background-cheat.png Binary files differnew file mode 100644 index 000000000..ad332f675 --- /dev/null +++ b/httemplate/images/background-cheat.png diff --git a/httemplate/images/black-gradient.png b/httemplate/images/black-gradient.png Binary files differnew file mode 100644 index 000000000..225732d16 --- /dev/null +++ b/httemplate/images/black-gradient.png diff --git a/httemplate/images/black-gray-corner.png b/httemplate/images/black-gray-corner.png Binary files differnew file mode 100644 index 000000000..17954cdd7 --- /dev/null +++ b/httemplate/images/black-gray-corner.png diff --git a/httemplate/images/black-gray-gradient.png b/httemplate/images/black-gray-gradient.png Binary files differnew file mode 100644 index 000000000..f5c318fe7 --- /dev/null +++ b/httemplate/images/black-gray-gradient.png diff --git a/httemplate/images/black-gray-side.png b/httemplate/images/black-gray-side.png Binary files differnew file mode 100644 index 000000000..f7a98a43d --- /dev/null +++ b/httemplate/images/black-gray-side.png diff --git a/httemplate/images/black-gray-top.png b/httemplate/images/black-gray-top.png Binary files differnew file mode 100644 index 000000000..ed0707573 --- /dev/null +++ b/httemplate/images/black-gray-top.png 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..48c58d561 --- /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..82d1f4715 --- /dev/null +++ b/httemplate/images/cvv2_amex.png diff --git a/httemplate/images/progressbar-empty.png b/httemplate/images/progressbar-empty.png Binary files differnew file mode 100644 index 000000000..318219c77 --- /dev/null +++ b/httemplate/images/progressbar-empty.png diff --git a/httemplate/images/progressbar-full.png b/httemplate/images/progressbar-full.png Binary files differnew file mode 100644 index 000000000..863d8e1ee --- /dev/null +++ b/httemplate/images/progressbar-full.png diff --git a/httemplate/images/red_telephone_mimooh_01.png b/httemplate/images/red_telephone_mimooh_01.png Binary files differnew file mode 100644 index 000000000..2212ff0e8 --- /dev/null +++ b/httemplate/images/red_telephone_mimooh_01.png diff --git a/httemplate/images/small-logo.png b/httemplate/images/small-logo.png Binary files differnew file mode 100644 index 000000000..1e415e6d8 --- /dev/null +++ b/httemplate/images/small-logo.png diff --git a/httemplate/index.html b/httemplate/index.html new file mode 100644 index 000000000..57d85a9fb --- /dev/null +++ b/httemplate/index.html @@ -0,0 +1,59 @@ +% my $conf = new FS::Conf; + +<% include('/elements/header.html', 'Billing Main' ) %> +% +% +% my $sth = dbh->prepare( +% #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) +% "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) +% WHERE ( history_action = 'insert' OR history_action = 'replace_new' ) +% AND history_user = ? +% ORDER BY history_date desc" # LIMIT 10 +% ) or die dbh->errstr; +% +% $sth->execute( getotaker() ) or die $sth->errstr; +% +% my %saw = (); +% my @custnums = grep { !$saw{$_}++ } map $_->[0], @{ $sth->fetchall_arrayref }; +% +% @custnums = splice(@custnums, 0, 10); +% +% if ( @custnums ) { +% +% + + + <% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% + + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=1>Customers I recently added or modified</TH> + </TR> +% foreach my $custnum ( @custnums ) { +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% next unless $cust_main; + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="view/cust_main.cgi?<% $custnum %>"><% $custnum %>: <% $cust_main->name %></A></TD> + </TR> +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% } + + + </TABLE> +% } + + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html new file mode 100644 index 000000000..d85f3b6c3 --- /dev/null +++ b/httemplate/misc/batch-cust_pay.html @@ -0,0 +1,395 @@ +<% include("/elements/header.html", 'Quick payment entry', + menubar( + 'Main Menu' => $p, #popurl(1), + ), + ( $cgi->param('error') ? '' : 'onload="addRow()"' ), + ) +%> +% if ( $cgi->param('error') ) { + + <FONT SIZE="+1" COLOR="#ff0000"><% $cgi->param('error') %></FONT><BR><BR> +% } + + + +<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.submit.disabled=true;"> + +<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> --> + +<SCRIPT TYPE="text/javascript"> + + function clearhint_custnum() { + + //this.style.color = '#000000'; + + if ( this.value == 'Not found' || this.value == 'Multiple' ) { + this.value = ''; + this.style.color = '#000000'; + } + + } + + function clearhint_customer() { + + this.style.color = '#000000'; + + if ( this.value == '(last name or company)' || this.value == 'Not found' ) + this.value = ''; + + } + + function search_custnum() { + + this.style.color = '#000000' + + var custnum_obj = this; + var searchrow = this.getAttribute('rownum'); + var custnum = this.value; + + if ( custnum == 'searching...' || custnum == 'Not found' || custnum == '' ) + return; + + if ( this.getAttribute('magic') == 'nosearch' ) { + this.setAttribute('magic', ''); + return; + } + + if ( ( rownum - searchrow ) == 1 ) { + addRow(); + } + var customer = document.getElementById('customer'+searchrow); + customer.value = 'searching...'; + customer.disabled = true; + customer.style.color = '#000000'; + customer.style.backgroundColor = '#dddddd'; + + var customer_select = document.getElementById('cust_select'+searchrow); + + //alert('search for custnum ' + custnum + ', row#' + searchrow ); + + customer.style.display = ''; + customer_select.style.display = 'none'; + + function search_custnum_update(name) { + + var name = eval('(' + name + ')' ); + + customer.disabled = false; + customer.style.backgroundColor = '#ffffff'; + + if ( name.length > 0 ) { + //alert('custnum found: ' + name); + customer.value = name; + customer.setAttribute('magic', 'nosearch'); + } else { + customer.value = 'Not found'; + customer.style.color = '#ff0000'; + custnum_obj.style.color = '#ff0000'; + + } + + } + + custnum_search( custnum, search_custnum_update ); + + } + + function search_customer() { + + var customer_obj = this; + var searchrow = this.getAttribute('rownum'); + var customer = this.value; + + if ( customer == 'searching...' || customer == 'Not found' || customer == '' ) + return; + + if ( this.getAttribute('magic') == 'nosearch' ) { + this.setAttribute('magic', ''); + return; + } + + if ( ( rownum - searchrow ) == 1 ) { + addRow(); + } + + var custnum_obj = document.getElementById('custnum'+searchrow); + custnum_obj.value = 'searching...'; + custnum_obj.disabled = true; + custnum_obj.style.color = '#000000'; + custnum_obj.style.backgroundColor = '#dddddd'; + + var customer_select = document.getElementById('cust_select'+searchrow); + + //alert('search for customer ' + customer + ', row#' + searchrow ); + + function search_customer_update(customers) { + + //alert('customers returned: ' + customers); + + var customerArray = eval('(' + customers + ')'); + + custnum_obj.disabled = false; + custnum_obj.style.backgroundColor = '#ffffff'; + + if ( customerArray.length == 0 ) { + + custnum_obj.value = 'Not found'; + custnum_obj.style.color = '#ff0000'; + customer_obj.style.color = '#ff0000'; + + customer_obj.style.display = ''; + customer_select.style.display = 'none'; + + + } else if ( customerArray.length == 1 ) { + + //alert('one customer found: ' + customerArray[0]); + + custnum_obj.value = customerArray[0][0]; + customer_obj.value = customerArray[0][1]; + + customer_obj.style.display = ''; + customer_select.style.display = 'none'; + + + } else { + + custnum_obj.value = 'Multiple'; // or something + custnum_obj.style.color = '#ff0000'; + + //alert('multiple customers found, have to create select dropdown'); + + //blank the current list + for ( var i = customer_select.length; i >= 0; i-- ) + customer_select.options[i] = null; + + opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000'); + + //add the multiple customers + for ( var s = 0; s < customerArray.length; s++ ) + opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000'); + + opt(customer_select, 'cancel', '(Edit search string)', '#000000'); + + customer_obj.style.display = 'none'; + + customer_select.style.display = ''; + + } + + } + + smart_search( customer, search_customer_update ); + + } + + function select_customer() { + + var custnum = this.options[this.selectedIndex].value; + var customer = this.options[this.selectedIndex].text; + + var searchrow = this.getAttribute('rownum'); + var custnum_obj = document.getElementById('custnum'+searchrow); + var customer_obj = document.getElementById('customer'+searchrow); + + if ( custnum == '' ) { + //this.style.color = '#ff0000'; + + } else if ( custnum == 'cancel' ) { + + custnum_obj.value = ''; + custnum_obj.style.color = '#000000'; + + this.style.display = 'none'; + customer_obj.style.display = ''; + customer_obj.focus(); + + } else { + + + custnum_obj.value = custnum; + custnum_obj.style.color = '#000000'; + + customer_obj.value = customer; + customer_obj.style.color = '#000000'; + + this.style.display = 'none'; + customer_obj.style.display = ''; + + } + + } + + function opt(what,value,text,color) { + var optionName = new Option(text, value, false, false); + optionName.style.color = color; + var length = what.length; + what.options[length] = optionName; + } + +</SCRIPT> + +<TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> + +<TR> + <TH>Cust #</TH> + <TH>Customer</TH> + <TH>Amount</TH> + <TH>Check #</TH> + <TH BGCOLOR="#e8e8e8"></TH> +</TR> +% my $row = 0; +% if ( $cgi->param('error') ) { +% my $param = $cgi->Vars; +% +% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { + + + <TR> + + <TD> + <INPUT TYPE="text" NAME="custnum<% $row %>" ID="custnum<% $row %>" SIZE=8 MAXLENGTH=12 VALUE="<% $param->{"custnum$row"} %>" rownum="<% $row %>"> + <SCRIPT TYPE="text/javascript"> + var custnum_input<% $row %> = document.getElementById("custnum<% $row %>"); + custnum_input<% $row %>.onfocus = clearhint_custnum; + custnum_input<% $row %>.onchange = search_custnum; + </SCRIPT> + </TD> + + <TD> + <INPUT TYPE="text" NAME="customer<% $row %>" ID="customer<% $row %>" SIZE=64 VALUE="<% $param->{"customer$row"} %>" rownum="<% $row %>"> + <SCRIPT TYPE="text/javascript"> + var customer_input<% $row %> = document.getElementById("customer<% $row %>"); + customer_input<% $row %>.onfocus = clearhint_customer; + customer_input<% $row %>.onclick = clearhint_customer; + customer_input<% $row %>.onchange = search_customer; + </SCRIPT> + <SELECT NAME="cust_select<% $row %>" ID="cust_select<% $row %>" rownum="<% $row %>" STYLE="color:#ff0000; display:none"> + </SELECT> + <SCRIPT TYPE="text/javascript"> + var customer_select<% $row %> = document.getElementById("cust_select<% $row %>"); + customer_select<% $row %>.onchange = select_customer; + </SCRIPT> + </TD> + + <TD> + $<INPUT TYPE="text" NAME="paid<% $row %>" SIZE=8 MAXLENGTH=8 VALUE="<% $param->{"paid$row"} %>" > + </TD> + + <TD> + <INPUT TYPE="text" NAME="payinfo<% $row %>" SIZE=10 VALUE="<% $param->{"payinfo$row"} %>" > + </TD> + + <TD BGCOLOR="#e8e8e8"> +% if ( $param->{"error$row"} ) { + + <FONT SIZE="-1" COLOR="#ff0000">Error: <% $param->{"error$row"} %></FONT> +% } + + </TD> + + </TR> +% } +% } + + +</TABLE> + +<!-- <BR> +<INPUT TYPE="button" VALUE="TEST addrow" onclick="addRow()"> --> + +<BR> +<INPUT TYPE="submit" NAME="submit" VALUE="Post payment batch"> + +</FORM> + + +<% include('/elements/xmlhttp.html', + 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi', + 'subs' => [qw( custnum_search smart_search )], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + var rownum = <% $row %>; + + function addRow() { + + var table = document.getElementById('OneTrueTable'); + var tablebody = table.getElementsByTagName('tbody').item(0); + + var row = document.createElement('TR'); + + var custnum_cell = document.createElement('TD'); + + var custnum_input = document.createElement('INPUT'); + custnum_input.setAttribute('name', 'custnum'+rownum); + custnum_input.setAttribute('id', 'custnum'+rownum); + custnum_input.setAttribute('size', 8); + custnum_input.setAttribute('maxlength', 12); + custnum_input.setAttribute('rownum', rownum); + custnum_input.onfocus = clearhint_custnum; + custnum_input.onchange = search_custnum; + custnum_cell.appendChild(custnum_input); + + row.appendChild(custnum_cell); + + var customer_cell = document.createElement('TD'); + + var customer_input = document.createElement('INPUT'); + customer_input.setAttribute('name', 'customer'+rownum); + customer_input.setAttribute('id', 'customer'+rownum); + customer_input.setAttribute('size', 64); + customer_input.setAttribute('value', '(last name or company)' ); + customer_input.setAttribute('rownum', rownum); + customer_input.onfocus = clearhint_customer; + customer_input.onclick = clearhint_customer; + customer_input.onchange = search_customer; + customer_cell.appendChild(customer_input); + + var customer_select = document.createElement('SELECT'); + customer_select.setAttribute('name', 'cust_select'+rownum); + customer_select.setAttribute('id', 'cust_select'+rownum); + customer_select.setAttribute('rownum', rownum); + customer_select.style.color = '#ff0000'; + customer_select.style.display = 'none'; + customer_select.onchange = select_customer; + customer_cell.appendChild(customer_select); + + row.appendChild(customer_cell); + + var paid_cell = document.createElement('TD'); + + var paid_text = document.createTextNode('$'); + paid_cell.appendChild(paid_text); + + var paid_input = document.createElement('INPUT'); + paid_input.setAttribute('name', 'paid'+rownum); + paid_input.setAttribute('size', 8); + paid_input.setAttribute('maxlength', 8); + paid_cell.appendChild(paid_input); + + row.appendChild(paid_cell); + + var payinfo_cell = document.createElement('TD'); + var payinfo_input = document.createElement('INPUT'); + payinfo_input.setAttribute('name', 'payinfo'+rownum); + payinfo_input.setAttribute('size', 10); + payinfo_cell.appendChild(payinfo_input); + row.appendChild(payinfo_cell); + + var error_cell = document.createElement('TD'); + error_cell.style.backgroundColor = '#e8e8e8'; + row.appendChild(error_cell); + + tablebody.appendChild(row); + + rownum++; + + } + +</SCRIPT> + +</BODY> +</HTML> diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi new file mode 100755 index 000000000..6e4cc265d --- /dev/null +++ b/httemplate/misc/bill.cgi @@ -0,0 +1,42 @@ +% +%#untaint custnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d*)$/; +%my $custnum = $1; +%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +%die "Can't find customer!\n" unless $cust_main; +% +%my $conf = new FS::Conf; +% +%my $error = $cust_main->bill( +%# 'time'=>$time +% ); +%#&eidiot($error) if $error; +% +%unless ( $error ) { +% $cust_main->apply_payments; +% $cust_main->apply_credits; +% +% $error = $cust_main->collect( +% # 'invoice-time'=>$time, +% #'batch_card'=> 'yes', +% #'batch_card'=> 'no', +% #'report_badcard'=> 'yes', +% #'retry_card' => 'yes', +% 'retry' => 'yes', +% 'realtime' => $conf->exists('realtime-backend'), +% ); +%} +%#&eidiot($error) if $error; +% +%if ( $error ) { +% + +<!-- mason kludge --> +% +% &idiot($error); +%} else { +% print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum"); +%} +% + diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi new file mode 100755 index 000000000..6f070a444 --- /dev/null +++ b/httemplate/misc/cancel-unaudited.cgi @@ -0,0 +1,36 @@ +% +% +%my $dbh = dbh; +% +%#untaint svcnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +% +%#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +%#die "Unknown svcnum!" unless $svc_acct; +% +%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "Unknown svcnum!" unless $cust_svc; +%my $cust_pkg = $cust_svc->cust_pkg; +%if ( $cust_pkg ) { +% &eidiot( 'This account has already been audited. Cancel the '. +% qq!<A HREF="${p}view/cust_main.cgi?!. $cust_pkg->custnum. +% '#cust_pkg'. $cust_pkg->pkgnum. '">'. +% 'package</A> instead.'); +%} +% +%my $error = $cust_svc->cancel; +% +%if ( $error ) { +% + +<!-- mason kludge --> +% +% &eidiot($error); +%} else { +% print $cgi->redirect(popurl(2)); +%} +% +% + diff --git a/httemplate/misc/cancel_pkg.cgi b/httemplate/misc/cancel_pkg.cgi new file mode 100755 index 000000000..00b421f10 --- /dev/null +++ b/httemplate/misc/cancel_pkg.cgi @@ -0,0 +1,16 @@ +% +% +%#untaint pkgnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +%my $pkgnum = $1; +% +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% +%my $error = $cust_pkg->cancel; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); +% +% + diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi new file mode 100755 index 000000000..8881746d1 --- /dev/null +++ b/httemplate/misc/catchall.cgi @@ -0,0 +1,134 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +% +%my($svc_domain, $svcnum, $pkgnum, $svcpart, $part_svc); +%if ( $cgi->param('error') ) { +% $svc_domain = new FS::svc_domain ( { +% map { $_, scalar($cgi->param($_)) } fields('svc_domain') +% } ); +% $svcnum = $svc_domain->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_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +% or die "Unknown (svc_domain) 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 { +% +% die "Invalid (svc_domain) svcnum!"; +% +% } +%} +% +%my %email; +%if ($pkgnum) { +% +% #find all possible user svcnums (and emails) +% +% #starting with that currently attached +% if ($svc_domain->catchall) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +% $email{$svc_domain->catchall} = $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'}) ) { +% push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +% } +% +% my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% my($custnum)=$cust_pkg->getfield('custnum'); +% my($i_cust_pkg); +% foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +% my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +% my($acct_svcpart); +% foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding +% #record(s) in cust_svc ( for this +% #pkgnum ! ) +% my($i_cust_svc); +% foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) { +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')}); +% $email{$svc_acct->getfield('svcnum')}=$svc_acct->email; +% } +% } +% } +% +%} else { +% +% my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +% $email{$svc_domain->catchall} = $svc_acct->email; +%} +% +%# add an absence of a catchall +%$email{''} = "(none)"; +% +%my $p1 = popurl(1); +%print header("Domain Catchall Edit", ''); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print qq!<FORM ACTION="${p1}process/catchall.cgi" METHOD=POST>!; +% +%#display +% +% #formatting +% print "<PRE>"; +% +%#svcnum +%print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +%print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>"; +% +%#pkgnum +%print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; +% +%#svcpart +%print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; +% +%my($domain,$catchall)=( +% $svc_domain->domain, +% $svc_domain->catchall, +%); +% +%print qq!<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">!; +% +%#catchall +%print qq!\n\nMail to <I>(anything)</I>@<B>$domain</B> forwards to <SELECT NAME="catchall" SIZE=1>!; +%foreach $_ (keys %email) { +% print "<OPTION", $_ eq $catchall ? " SELECTED" : "", +% qq! VALUE="$_">$email{$_}!; +%} +%print "</SELECT>"; +% +% #formatting +% print "</PRE>\n"; +% +%print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!; +% +%print <<END; +% +% </FORM> +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/misc/cdr-import.html b/httemplate/misc/cdr-import.html new file mode 100644 index 000000000..5e9e2690d --- /dev/null +++ b/httemplate/misc/cdr-import.html @@ -0,0 +1,16 @@ +<% include("/elements/header.html",'Call Detail Record Import') %> +<FORM ACTION="process/cdr-import.html" METHOD="POST" ENCTYPE="multipart/form-data"> +Import a CSV file containing Call Detail Records (CDRs).<BR><BR> +CDR Format: <SELECT NAME="format"> +<OPTION VALUE="asterisk">Asterisk (untested)</OPTION> +<OPTION VALUE="unitel">Unitel/RSLCOM</OPTION> +<OPTION VALUE="ams">AMS</OPTION> +</SELECT><BR><BR> + +Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR> + +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi new file mode 100755 index 000000000..17dc8b859 --- /dev/null +++ b/httemplate/misc/change_pkg.cgi @@ -0,0 +1,67 @@ +<!-- mason kludge --> +% +% +%my $pkgnum; +%if ( $cgi->param('error') ) { +% #$custnum = $cgi->param('custnum'); +% #%remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg'); +% $pkgnum = ($cgi->param('remove_pkg'))[0]; +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% #$custnum = $1; +% $pkgnum = $1; +% #%remove_pkg = (); +%} +% +%my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ) +% or die "unknown pkgnum $pkgnum"; +%my $custnum = $cust_pkg->custnum; +% +%my $conf = new FS::Conf; +% +%my $p1 = popurl(1); +% +%my $cust_main = $cust_pkg->cust_main +% or die "can't get cust_main record for custnum ". $cust_pkg->custnum. +% " ( pkgnum ". cust_pkg->pkgnum. ")"; +%my $agent = $cust_main->agent; +% +%print header("Change Package", menubar( +% "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +% 'Main Menu' => $p, +%)); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT><BR><BR>" +% if $cgi->param('error'); +% +%my $part_pkg = $cust_pkg->part_pkg; +% +%print small_custview( $cust_main, $conf->config('countrydefault') ). +% qq!<FORM ACTION="${p}edit/process/cust_pkg.cgi" METHOD=POST>!. +% qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!. +% qq!<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="$pkgnum">!. +% '<BR>Current package: '. $part_pkg->pkg. ' - '. $part_pkg->comment. +% qq!<BR>New package: <SELECT NAME="new_pkgpart"><OPTION VALUE=0></OPTION>!; +% +%foreach my $part_pkg ( +% grep { ! $_->disabled && $_->pkgpart != $cust_pkg->pkgpart } +% map { $_->part_pkg } $agent->agent_type->type_pkgs +%) { +% my $pkgpart = $part_pkg->pkgpart; +% print qq!<OPTION VALUE="$pkgpart"!; +% print ' SELECTED' if $cgi->param('error') +% && $cgi->param('new_pkgpart') == $pkgpart; +% print qq!>$pkgpart: !. $part_pkg->pkg. ' - '. $part_pkg->comment. '</OPTION>'; +%} +% +%print <<END; +%</SELECT> +%<BR><BR><INPUT TYPE="submit" VALUE="Change package"> +% </FORM> +% </BODY> +%</HTML> +%END +% + diff --git a/httemplate/misc/counties.cgi b/httemplate/misc/counties.cgi new file mode 100644 index 000000000..c9eb98500 --- /dev/null +++ b/httemplate/misc/counties.cgi @@ -0,0 +1,18 @@ +% +% +% my( $state, $country ) = $cgi->param('arg'); +% +% my @counties = +% sort +% map { s/[\n\r]//g; $_; } +% map { $_->county; } +% qsearch( 'cust_main_county', +% { 'state' => $state, +% 'country' => $country, +% }, +% ) +% ; +% +% +% +[ <% join(', ', map { qq("$_") } @counties) %> ] diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi new file mode 100755 index 000000000..d29e4f5fc --- /dev/null +++ b/httemplate/misc/cust_main-cancel.cgi @@ -0,0 +1,23 @@ +% +% +%my $custnum; +%my $ban = ''; +%if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { +% $custnum = $1; +% $ban = $cgi->param('ban'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ || die "Illegal custnum"; +% $custnum = $1; +%} +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% +%my @errors = $cust_main->cancel( 'ban' => $ban ); +%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/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi new file mode 100644 index 000000000..f7a8d8b52 --- /dev/null +++ b/httemplate/misc/cust_main-import.cgi @@ -0,0 +1,67 @@ +<% include("/elements/header.html",'Batch Customer Import') %> + +<FORM ACTION="process/cust_main-import.cgi" METHOD="post" ENCTYPE="multipart/form-data"> + +Import a CSV file containing customer records. +<BR><BR> + +<!-- Simple file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i> +<BR><BR> --> + +Extended file format is CSV, with the following field order: <i>agent_custid, refnum[1], last, first, address1, address2, city, state, zip, country, daytime, night, ship_last, ship_first, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, username, _password</i> +<BR><BR> + +[1] This field has special treatment upon import: If a string is passed instead +of an integer, the string is searched for and if necessary auto-created in the +target table. +<BR><BR> + +<% &ntable("#cccccc") %> + +<% include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "<B>Agent</B>", + 'empty_label' => 'Select agent', + ) +%> + +<TR> + <TH ALIGN="right">Format</TH> + <TD> + <SELECT NAME="format"> +<!-- <OPTION VALUE="simple">Simple --> + <OPTION VALUE="extended" SELECTED>Extended + </SELECT> + </TD> +</TR> + +<TR> + <TH ALIGN="right">CSV filename</TH> + <TD><INPUT TYPE="file" NAME="csvfile"></TD> +</TR> +% #include('/elements/tr-select-part_referral.html') +% + + +<!-- +<TR> + <TH>First package</TH> + <TD> + <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION> +% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { + + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> +% } + + </SELECT> + </TD> +</TR> +--> + +</TABLE> +<BR><BR> + +<INPUT TYPE="submit" VALUE="Import"> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi new file mode 100644 index 000000000..cd4441e0b --- /dev/null +++ b/httemplate/misc/cust_main-import_charges.cgi @@ -0,0 +1,14 @@ +<!-- mason kludge --> +<% include("/elements/header.html",'Batch Customer Charge') %> +<FORM ACTION="process/cust_main-import_charges.cgi" METHOD="post" ENCTYPE="multipart/form-data"> +Import a CSV file containing customer charges.<BR><BR> +Default file format is CSV, with the following field order: <i>custnum, amount, description</i><BR><BR> +If <i>amount</i> is negative, a credit will be applied instead.<BR><BR> +<BR><BR> + + CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR> + <INPUT TYPE="submit" VALUE="Import"> + </FORM> + </BODY> +<HTML> + diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi new file mode 100755 index 000000000..e4756a922 --- /dev/null +++ b/httemplate/misc/delete-cust_credit.cgi @@ -0,0 +1,17 @@ +% +% +%#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/delete-cust_pay.cgi b/httemplate/misc/delete-cust_pay.cgi new file mode 100755 index 000000000..1fda82e2a --- /dev/null +++ b/httemplate/misc/delete-cust_pay.cgi @@ -0,0 +1,17 @@ +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); +%my $custnum = $cust_pay->custnum; +% +%my $error = $cust_pay->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi new file mode 100755 index 000000000..378f69e61 --- /dev/null +++ b/httemplate/misc/delete-customer.cgi @@ -0,0 +1,61 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +%die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); +% +%my($custnum, $new_custnum); +%if ( $cgi->param('error') ) { +% $custnum = $cgi->param('custnum'); +% $new_custnum = $cgi->param('new_custnum'); +%} else { +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/ or die "Illegal query: $query"; +% $custnum = $1; +% $new_custnum = ''; +%} +%my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +% or die "Customer not found: $custnum"; +% +%print header('Delete customer'); +% +%print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +% "</FONT>" +% if $cgi->param('error'); +% +%print +% qq!<form action="!, popurl(1), qq!process/delete-customer.cgi" method=post>!, +% qq!<input type="hidden" name="custnum" value="$custnum">!; +% +%if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) { +% print "Move uncancelled packages to customer number ", +% qq!<input type="text" name="new_custnum" value="$new_custnum"><br><br>!; +%} +% +%print <<END; +%This will <b>completely remove</b> all traces of this customer record. This +%is <B>not</B> what you want if this is a real customer who has simply +%canceled service with you. For that, cancel all of the customer's packages. +%(you can optionally hide cancelled customers with the <a href="../config/config-view.cgi#hidecancelledcustomers">hidecancelledcustomers</a> configuration option) +%<br> +%<br>Are you <b>absolutely sure</b> you want to delete this customer? +%<br><input type="submit" value="Yes"> +%</form></body></html> +%END +% +%#Deleting a customer you have financial records on (i.e. credits) is +%#typically considered fraudulant bookkeeping. Remember, deleting +%#customers should ONLY be used for completely bogus records. You should +%#NOT delete real customers who simply discontinue service. +%# +%#For real customers who simply discontinue service, cancel all of the +%#customer's packages. Customers with all cancelled packages are not +%#billed. There is no need to take further action to prevent billing on +%#customers with all cancelled packages. +%# +%#Also see the "hidecancelledcustomers" and "hidecancelledpackages" +%#configuration options, which will allow you to surpress the display of +%#cancelled customers and packages, respectively. +% +% + diff --git a/httemplate/misc/delete-domain_record.cgi b/httemplate/misc/delete-domain_record.cgi new file mode 100755 index 000000000..cccce357e --- /dev/null +++ b/httemplate/misc/delete-domain_record.cgi @@ -0,0 +1,16 @@ +% +% +%#untaint recnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal recnum"; +%my $recnum = $1; +% +%my $domain_record = qsearchs('domain_record',{'recnum'=>$recnum}); +% +%my $error = $domain_record->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/svc_domain.cgi?". $domain_record->svcnum); +% +% + diff --git a/httemplate/misc/delete-part_export.cgi b/httemplate/misc/delete-part_export.cgi new file mode 100755 index 000000000..16389a90c --- /dev/null +++ b/httemplate/misc/delete-part_export.cgi @@ -0,0 +1,16 @@ +% +% +%#untaint exportnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal exportnum"; +%my $exportnum = $1; +% +%my $part_export = qsearchs('part_export',{'exportnum'=>$exportnum}); +% +%my $error = $part_export->delete; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "browse/part_export.cgi"); +% +% + diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi new file mode 100644 index 000000000..038aa2049 --- /dev/null +++ b/httemplate/misc/download-batch.cgi @@ -0,0 +1,132 @@ +% +% +%my $conf=new FS::Conf; +% +%#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes +%http_header('Content-Type' => 'text/plain' ); +% +%my $batchnum; +%if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) { +% $batchnum = $1; +%} else { +% die "No batch number (bad URL) \n"; +%} +% +%my $format; +%if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { +% $format = $1; +%} else { +% $format = $conf->config('batch-default_format'); +%} +% +%my $oldAutoCommit = $FS::UID::AutoCommit; +%local $FS::UID::AutoCommit = 0; +%my $dbh = dbh; +% +%my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} ); +%die "No pending batch. \n" unless $pay_batch; +% +%my %batchhash = $pay_batch->hash; +%$batchhash{'status'} = 'I'; +%$batchhash{'download'} = time unless $batchhash{'download'}; +%my $new = new FS::pay_batch \%batchhash; +%my $error = $new->replace($pay_batch); +%die "error updating batch status: $error\n" if $error; +% +%my $batchtotal=0; +%my $batchcount=0; +% +%my (@date)=localtime($new->download); +%my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1); +%my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1). +% sprintf("%02d", $date[5] % 100); +% +%if ($format eq "BoM") { +% +% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = +% $conf->config("batchconfig-$format"); +% +<% sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""). + sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) + %> +% +% +%}elsif ($format eq "PAP"){ +% +% my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = +% $conf->config("batchconfig-$format"); +% +<% sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"") + + %> +% +% +%}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ +%# 1; +%}else{ +% die "Unknown format for batch in batchconfig. \n"; +%} +% +% +%for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } +% qsearch('cust_pay_batch', +% {'batchnum'=>$pay_batch->batchnum} ) +%) { +% +% $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; +% my( $mon, $y ) = ( $2, $1 ); +% $mon = "0$mon" if $mon < 10; +% my $exp = "$mon$y"; +% $batchcount++; +% $batchtotal += $cust_pay_batch->amount; +% +% if ($format eq "BoM") { +% +% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); +% +<% sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->paybatchnum) %> +% +% +% } elsif ($format eq "PAP"){ +% +% my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); +% +<% sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %> +% +% +% } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") { +% +% +,,,,<% $cust_pay_batch->payinfo %>,<% $exp %>,<% $cust_pay_batch->amount %>,<% $cust_pay_batch->paybatchnum %> +% +% +% } else { +% die "I'm already dead, but you did not know that.\n"; +% } +% +%} +% +%if ($format eq "BoM") { +% +% +<% sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ). + sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %> +% +% +%} elsif ($format eq "PAP"){ +% +% +<% sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %> +% +% +%} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ +% #1; +%} else { +% die "I'm already dead (again), but you did not know that.\n"; +%} +% +%$dbh->commit or die $dbh->errstr if $oldAutoCommit; +% +% + + diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi new file mode 100644 index 000000000..e8f4b6f38 --- /dev/null +++ b/httemplate/misc/dump.cgi @@ -0,0 +1,20 @@ +% +% 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..8a3dd90b1 --- /dev/null +++ b/httemplate/misc/email-invoice.cgi @@ -0,0 +1,18 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->email($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% + diff --git a/httemplate/misc/email_invoice_events.cgi b/httemplate/misc/email_invoice_events.cgi new file mode 100644 index 000000000..ba6e72c1a --- /dev/null +++ b/httemplate/misc/email_invoice_events.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/email_invoices.cgi b/httemplate/misc/email_invoices.cgi new file mode 100644 index 000000000..6c2103f7b --- /dev/null +++ b/httemplate/misc/email_invoices.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/expire_pkg.cgi b/httemplate/misc/expire_pkg.cgi new file mode 100755 index 000000000..55364c652 --- /dev/null +++ b/httemplate/misc/expire_pkg.cgi @@ -0,0 +1,56 @@ +<!-- mason kludge --> +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $pkgnum = $1; +% +%#get package record +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%die "Unknown pkgnum $pkgnum" unless $cust_pkg; +%my $part_pkg = $cust_pkg->part_pkg; +% +%my $custnum = $cust_pkg->getfield('custnum'); +% +%my $date = $cust_pkg->expire ? time2str('%D', $cust_pkg->expire) : ''; +% +% + + +<% include("/elements/header.html",'Expire package', menubar( + "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + '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> + +<% $pkgnum %>: <% $part_pkg->pkg. ' - '. $part_pkg->comment %> + +<FORM NAME="formname" ACTION="process/expire_pkg.cgi" METHOD="post"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<TABLE> + <TR> + <TD>Cancel package on </TD> + <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<% $date %>"> + <IMG SRC="<% $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date"> + <BR><I>m/d/y</I> + </TD> + </TR> +</TABLE> + +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "expire_date", + ifFormat: "%m/%d/%Y", + button: "expire_button", + align: "BR" + }); +</SCRIPT> + +<INPUT TYPE="submit" VALUE="Cancel later"> +</FORM> +</BODY> +</HTML> diff --git a/httemplate/misc/fax-invoice.cgi b/httemplate/misc/fax-invoice.cgi new file mode 100755 index 000000000..1ddc23ece --- /dev/null +++ b/httemplate/misc/fax-invoice.cgi @@ -0,0 +1,18 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->fax($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% + diff --git a/httemplate/misc/fax_invoice_events.cgi b/httemplate/misc/fax_invoice_events.cgi new file mode 100644 index 000000000..deb78d456 --- /dev/null +++ b/httemplate/misc/fax_invoice_events.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/fax_invoices.cgi b/httemplate/misc/fax_invoices.cgi new file mode 100644 index 000000000..4bdac970c --- /dev/null +++ b/httemplate/misc/fax_invoices.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/inventory_item-import.html b/httemplate/misc/inventory_item-import.html new file mode 100644 index 000000000..87c6af34c --- /dev/null +++ b/httemplate/misc/inventory_item-import.html @@ -0,0 +1,21 @@ +% +% +%my $classnum = $cgi->param('classnum'); +%$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; +%$classnum = $1; +%my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } ); +% +% +<% include("/elements/header.html", $inventory_class->classname. 's') %> + +<FORM ACTION="process/inventory_item-import.html" METHOD="POST" ENCTYPE="multipart/form-data"> +<INPUT TYPE="hidden" NAME="classnum" VALUE="<% $classnum %>"> +Import a file containing <% $inventory_class->classname %>s, one per line.<BR><BR> + +Filename: <INPUT TYPE="file" NAME="filename"><BR><BR> + +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi new file mode 100755 index 000000000..1d1f5e133 --- /dev/null +++ b/httemplate/misc/link.cgi @@ -0,0 +1,84 @@ +<!-- mason kludge --> +% +% +%my %link_field = ( +% 'svc_acct' => 'username', +% 'svc_domain' => 'domain', +%); +% +%my %link_field2 = ( +% 'svc_acct' => { label => 'Domain', +% field => 'domsvc', +% type => 'select', +% select_table => 'svc_domain', +% select_key => 'svcnum', +% select_label => 'domain' +% }, +%); +% +%my($query) = $cgi->keywords; +%my($pkgnum, $svcpart) = ('', ''); +%foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart +% $pkgnum=$1 if /^pkgnum(\d+)$/; +% $svcpart=$1 if /^svcpart(\d+)$/; +%} +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); +%my $svc = $part_svc->getfield('svc'); +%my $svcdb = $part_svc->getfield('svcdb'); +%my $link_field = $link_field{$svcdb}; +%my $link_field2 = $link_field2{$svcdb}; +% +% + + +<% include("/elements/header.html","Link to existing $svc") %> +<FORM ACTION="<% popurl(1) %>process/link.cgi" METHOD=POST> +% if ( $link_field ) { + + <INPUT TYPE="hidden" NAME="svcnum" VALUE=""> + <INPUT TYPE="hidden" NAME="link_field" VALUE="<% $link_field %>"> + <% $link_field %> of existing service: <INPUT TYPE="text" NAME="link_value"> + <BR> +% if ( $link_field2 ) { + + <INPUT TYPE="hidden" NAME="link_field2" VALUE="<% $link_field2->{field} %>"> + <% $link_field2->{'label'} %> of existing service: +% if ( $link_field2->{'type'} eq 'select' ) { +% if ( $link_field2->{'select_table'} ) { + + <SELECT NAME="link_value2"> + <OPTION> </OPTION> +% foreach my $r ( qsearch( $link_field2->{'select_table'}, {})) { +% my $key = $link_field2->{'select_key'}; +% my $label = $link_field2->{'select_label'}; + + <OPTION VALUE="<% $r->$key() %>"><% $r->$label() %></OPTION> +% } + + </SELECT> +% } else { + + Don't know how to process secondary link field for <% $svcdb %> + (type=>select but no select_table) +% } +% } else { + + Don't know how to process secondary link field for <% $svcdb %> + (unknown type <% $link_field2->{'type'} %>) +% } + + <BR> +% } +% } else { + + Service # of existing service: <INPUT TYPE="text" NAME="svcnum" VALUE=""> +% } + + +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> +<BR><INPUT TYPE="submit" VALUE="Link"> + </FORM> + </BODY> +</HTML> diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi new file mode 100644 index 000000000..fc249a2ab --- /dev/null +++ b/httemplate/misc/meta-import.cgi @@ -0,0 +1,73 @@ +<!-- mason kludge --> +<% include("/elements/header.html",'Import') %> +<FORM ACTION="process/meta-import.cgi" METHOD="post" ENCTYPE="multipart/form-data"> +Import data from a DBI data source<BR><BR> +% +% #false laziness with edit/cust_main.cgi +% my @agents = qsearch( 'agent', {} ); +% die "No agents created!" unless @agents; +% my $agentnum = $agents[0]->agentnum; #default to first +% +% if ( scalar(@agents) == 1 ) { +% + + <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>"> +% } else { + + <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1"> +% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { + + <OPTION VALUE="<% $agent->agentnum %>" <% " SELECTED"x($agent->agentnum==$agentnum) %>><% $agent->agent %></OPTION> +% } + + </SELECT><BR><BR> +% } +% +% my @referrals = qsearch('part_referral',{}); +% die "No advertising sources created!" unless @referrals; +% my $refnum = $referrals[0]->refnum; #default to first +% +% if ( scalar(@referrals) == 1 ) { +% + + <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>"> +% } else { + + <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1"> +% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { + + <OPTION VALUE="<% $referral->refnum %>" <% " SELECTED"x($referral->refnum==$refnum) %>><% $referral->refnum %>: <% $referral->referral %></OPTION> +% } + + </SELECT><BR><BR> +% } + + + First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION> +% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { + + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION> +% } + +</SELECT><BR><BR> + + <table> + <tr> + <td align="right">DBI data source: </td> + <td><INPUT TYPE="text" NAME="data_source"></td> + </tr> + <tr> + <td align="right">DBI username: </td> + <td><INPUT TYPE="text" NAME="username"></td> + </tr> + <tr> + <td align="right">DBI password: </td> + <td><INPUT TYPE="text" NAME="password"></td> + </tr> + </table> + <INPUT TYPE="submit" VALUE="Import"> + + </FORM> + </BODY> +<HTML> + diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi new file mode 100644 index 000000000..4c6ae3349 --- /dev/null +++ b/httemplate/misc/payment.cgi @@ -0,0 +1,207 @@ +% +% my %type = ( 'CARD' => 'credit card', +% 'CHEK' => 'electronic check (ACH)', +% ); +% +% $cgi->param('payby') =~ /^(CARD|CHEK)$/ +% or die "unknown payby ". $cgi->param('payby'); +% my $payby = $1; +% +% $cgi->param('custnum') =~ /^(\d+)$/ +% or die "illegal custnum ". $cgi->param('custnum'); +% my $custnum = $1; +% +% my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); +% die "unknown custnum $custnum" unless $cust_main; +% +% my $balance = $cust_main->balance; +% +% my $payinfo = ''; +% +% #false laziness w/selfservice make_payment.html shortcut for one-country +% my $conf = new FS::Conf; +% my %states = map { $_->state => 1 } +% qsearch('cust_main_county', { +% 'country' => $conf->config('countrydefault') || 'US' +% } ); +% my @states = sort { $a cmp $b } keys %states; +% +% my $paybatch = "webui-payment-". time. "-$$-". rand() * 2**32; +% +% + +<% include( '/elements/header.html', "Process $type{$payby} payment" ) %> +<% include( '/elements/small_custview.html', $cust_main ) %> +<FORM NAME="OneTrueForm" ACTION="process/payment.cgi" METHOD="POST" onSubmit="document.OneTrueForm.process.disabled=true"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> +<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> +<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>"> + +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript"> +function OLiframeContent(src, width, height, name) { + return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"' + +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">' + +'<div>[iframe not supported]</div></iframe>'); +} +</SCRIPT> +% #include( '/elements/table.html', '#cccccc' ) + +<% ntable('#cccccc') %> + <TR> + <TD ALIGN="right">Payment amount</TD> + <TD> + <TABLE><TR><TD BGCOLOR="#ffffff"> + $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<% $balance > 0 ? sprintf("%.2f", $balance) : '' %>"> + </TD></TR></TABLE> + </TD> + </TR> +% if ( $payby eq 'CARD' ) { +% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' ); +% my $payname = $cust_main->first. ' '. $cust_main->getfield('last'); +% my $address1 = $cust_main->address1; +% my $address2 = $cust_main->address2; +% my $city = $cust_main->city; +% my $state = $cust_main->state; +% my $zip = $cust_main->zip; +% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { +% $payinfo = $cust_main->payinfo; +% $paycvv = $cust_main->paycvv; +% ( $month, $year ) = $cust_main->paydate_monthyear; +% $payname = $cust_main->payname if $cust_main->payname; +% } +% + + <TR> + <TD ALIGN="right">Card number</TD> + <TD> + <TABLE> + <TR> + <TD> + <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%$payinfo%>"> </TD> + <TD>Exp.</TD> + <TD> + <SELECT NAME="month"> +% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { + + <OPTION<% $_ == $month ? ' SELECTED' : '' %>><% $_ %> +% } + + </SELECT> + </TD> + <TD> / </TD> + <TD> + <SELECT NAME="year"> +% my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { + + <OPTION<% $_ == $year ? ' SELECTED' : '' %>><% $_ %> +% } + + </SELECT> + </TD> + </TR> + </TABLE> + </TD> + </TR> + <TR> + <TD ALIGN="right">CVV2</TD> + <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<% $paycvv %>" SIZE=4 MAXLENGTH=4> + (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>) + </TD> + </TR> + <TR> + <TD ALIGN="right">Exact name on card</TD> + <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%$payname%>"></TD> + </TR><TR> + <TD ALIGN="right">Card billing address</TD> + <TD> + <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%$address1%>"> + </TD> + </TR><TR> + <TD ALIGN="right">Address line 2</TD> + <TD> + <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%$address2%>"> + </TD> + </TR><TR> + <TD ALIGN="right">City</TD> + <TD> + <TABLE> + <TR> + <TD> + <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%$city%>"> + </TD> + <TD>State</TD> + <TD> + <SELECT NAME="state"> +% for ( @states ) { + + <OPTION<% $_ eq $state ? ' SELECTED' : '' %>><% $_ %> +% } + + </SELECT> + </TD> + <TD>Zip</TD> + <TD> + <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%$zip%>"> + </TD> + </TR> + </TABLE> + </TD> + </TR> +% } elsif ( $payby eq 'CHEK' ) { +% my( $payinfo1, $payinfo2, $payname, $ss ) = ( '', '', '', '' ); +% if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { +% $cust_main->payinfo =~ /^(\d+)\@(\d+)$/ +% or die "unparsable payinfo ". $cust_main->payinfo; +% ($payinfo1, $payinfo2) = ($1, $2); +% $payname = $cust_main->payname; +% $ss = $cust_main->ss; +% } +% + + <INPUT TYPE="hidden" NAME="month" VALUE="12"> + <INPUT TYPE="hidden" NAME="year" VALUE="2037"> + <TR> + <TD ALIGN="right">Account number</TD> + <TD><INPUT TYPE="text" SIZE=10 NAME="payinfo1" VALUE="<%$payinfo1%>"></TD> + </TR> + <TR> + <TD ALIGN="right">ABA/Routing number</TD> + <TD> + <INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="<%$payinfo2%>"> + (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>) + </TD> + </TR> + <TR> + <TD ALIGN="right">Bank name</TD> + <TD><INPUT TYPE="text" NAME="payname" VALUE="<%$payname%>"></TD> + </TR> + <TR> + <TD ALIGN="right"> + Account holder<BR> + Social security or tax ID # + </TD> + <TD><INPUT TYPE="text" NAME="ss" VALUE="<%$ss%>"></TD> + </TR> +% } + + +<TR> + <TD COLSPAN=2> + <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1"> + Remember this information + </TD> +</TR><TR> + <TD COLSPAN=2> + <INPUT TYPE="checkbox"<% ( ( $payby eq 'CARD' && $cust_main->payby ne 'DCRD' ) || ( $payby eq 'CHEK' && $cust_main->payby eq 'CHEK' ) ) ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }"> + Charge future payments to this <% $type{$payby} %> automatically + </TD> +</TR> +</TABLE> +<BR> +<INPUT TYPE="submit" NAME="process" VALUE="Process payment"> +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi new file mode 100755 index 000000000..511bdce19 --- /dev/null +++ b/httemplate/misc/print-invoice.cgi @@ -0,0 +1,18 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $template = $2; +%my $invnum = $3; +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Can't find invoice!\n" unless $cust_bill; +% +%$cust_bill->print($template); +% +%my $custnum = $cust_bill->getfield('custnum'); +% +%print $cgi->redirect("${p}view/cust_main.cgi?$custnum"); +% +% + diff --git a/httemplate/misc/print_invoice_events.cgi b/httemplate/misc/print_invoice_events.cgi new file mode 100644 index 000000000..913e2683f --- /dev/null +++ b/httemplate/misc/print_invoice_events.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi; + +<% $server->process %> diff --git a/httemplate/misc/print_invoices.cgi b/httemplate/misc/print_invoices.cgi new file mode 100644 index 000000000..826a081fd --- /dev/null +++ b/httemplate/misc/print_invoices.cgi @@ -0,0 +1,4 @@ +% +%my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint', $cgi; +% +<% $server->process %> diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi new file mode 100644 index 000000000..e4d1bbff5 --- /dev/null +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -0,0 +1,45 @@ +% +% my $param = $cgi->Vars; +% +% #my $paybatch = $param->{'paybatch'}; +% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); +% +% my @cust_pay = (); +% #my $row = 0; +% #while ( exists($param->{"custnum$row"}) ) { +% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { +% push @cust_pay, new FS::cust_pay { +% 'custnum' => $param->{"custnum$row"}, +% 'paid' => $param->{"paid$row"}, +% 'payby' => 'BILL', +% 'payinfo' => $param->{"payinfo$row"}, +% 'paybatch' => $paybatch, +% } +% if $param->{"custnum$row"} +% || $param->{"paid$row"} +% || $param->{"payinfo$row"}; +% #$row++; +% } +% +% my @errors = FS::cust_pay->batch_insert(@cust_pay); +% my $num_errors = scalar(grep $_, @errors); +% +% if ( $num_errors ) { +% +% $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : ''). +% ' - Batch not processed, correct and resubmit' +% ); +% +% my $erow=0; +% $cgi->param('error'. $erow++, shift @errors) while @errors; +% +% +<% $cgi->redirect($p.'batch-cust_pay.html?'. $cgi->query_string) + + %> +% } else { +% +% +<% $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %> +% } + diff --git a/httemplate/misc/process/catchall.cgi b/httemplate/misc/process/catchall.cgi new file mode 100755 index 000000000..f2899c720 --- /dev/null +++ b/httemplate/misc/process/catchall.cgi @@ -0,0 +1,34 @@ +% +% +%$FS::svc_domain::whois_hack=1; +% +%$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +%my $svcnum =$1; +% +%my $old = qsearchs('svc_domain',{'svcnum'=>$svcnum}) if $svcnum; +% +%my $new = new FS::svc_domain ( { +% map { +% ($_, scalar($cgi->param($_))); +% } ( fields('svc_domain'), qw( pkgnum svcpart ) ) +%} ); +% +%$new->setfield('action' => 'M'); +% +%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). "catchall.cgi?". $cgi->query_string ); +%} else { +% print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +%} +% +% + diff --git a/httemplate/misc/process/cdr-import.html b/httemplate/misc/process/cdr-import.html new file mode 100644 index 000000000..68edaa21c --- /dev/null +++ b/httemplate/misc/process/cdr-import.html @@ -0,0 +1,30 @@ +% +% +% my $fh = $cgi->upload('csvfile'); +% +% my $error = defined($fh) +% ? FS::cdr::batch_import( { +% 'filehandle' => $fh, +% 'format' => $cgi->param('format'), +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> + <!-- XXX redirect to batch search like the payment entry... --> + <% include("/elements/footer.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi new file mode 100644 index 000000000..a5ede99a1 --- /dev/null +++ b/httemplate/misc/process/cust_main-import.cgi @@ -0,0 +1,35 @@ +% +% +% my $fh = $cgi->upload('csvfile'); +% #warn $cgi; +% #warn $fh; +% +% my $error = defined($fh) +% ? FS::cust_main::batch_import( { +% filehandle => $fh, +% agentnum => scalar($cgi->param('agentnum')), +% refnum => scalar($cgi->param('refnum')), +% pkgpart => scalar($cgi->param('pkgpart')), +% #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2 +% # city state zip comments )], +% 'format' => scalar($cgi->param('format')), +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/cust_main-import_charges.cgi b/httemplate/misc/process/cust_main-import_charges.cgi new file mode 100644 index 000000000..e0ede576b --- /dev/null +++ b/httemplate/misc/process/cust_main-import_charges.cgi @@ -0,0 +1,30 @@ +% +% +% my $fh = $cgi->upload('csvfile'); +% #warn $cgi; +% #warn $fh; +% +% my $error = defined($fh) +% ? FS::cust_main::batch_charge( { +% filehandle => $fh, +% 'fields' => [qw( custnum amount pkg )], +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import_charges.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> +% +% } +% + diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi new file mode 100755 index 000000000..d0d237ee8 --- /dev/null +++ b/httemplate/misc/process/delete-customer.cgi @@ -0,0 +1,30 @@ +% +% +%my $conf = new FS::Conf; +%die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); +% +%$cgi->param('custnum') =~ /^(\d+)$/; +%my $custnum = $1; +%my $new_custnum; +%if ( $cgi->param('new_custnum') ) { +% $cgi->param('new_custnum') =~ /^(\d+)$/ +% or die "Illegal new customer number: ". $cgi->param('new_custnum'); +% $new_custnum = $1; +%} else { +% $new_custnum = ''; +%} +%my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +% or die "Customer not found: $custnum"; +% +%my $error = $cust_main->delete($new_custnum); +% +%if ( $error ) { +% $cgi->param('error', $error); +% print $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ); +%} elsif ( $new_custnum ) { +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum"); +%} else { +% print $cgi->redirect(popurl(3)); +%} +% + diff --git a/httemplate/misc/process/expire_pkg.cgi b/httemplate/misc/process/expire_pkg.cgi new file mode 100755 index 000000000..d1963e2f5 --- /dev/null +++ b/httemplate/misc/process/expire_pkg.cgi @@ -0,0 +1,26 @@ +% +% +%#untaint date & pkgnum +% +%my $date; +%if ( $cgi->param('date') ) { +% str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date"; +% $date=$1; +%} else { +% $date=''; +%} +% +%$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum"; +%my $pkgnum = $1; +% +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%my %hash = $cust_pkg->hash; +%$hash{expire}=$date; +%my $new = new FS::cust_pkg ( \%hash ); +%my $error = $new->replace($cust_pkg); +%&eidiot($error) if $error; +% +%print $cgi->redirect(popurl(3). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); +% +% + diff --git a/httemplate/misc/process/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html new file mode 100644 index 000000000..f6159dc99 --- /dev/null +++ b/httemplate/misc/process/inventory_item-import.html @@ -0,0 +1,31 @@ +% +% +% my $fh = $cgi->upload('filename'); +% +% my $error = defined($fh) +% ? FS::inventory_item::batch_import( { +% 'filehandle' => $fh, +% 'classnum' => $cgi->param('classnum'), +% } ) +% : 'No file'; +% +% if ( $error ) { +% + + <!-- mason kludge --> +% +% eidiot($error); +%# $cgi->param('error', $error); +%# print $cgi->redirect( "${p}cust_main-import.cgi +% } else { +% + + <!-- mason kludge --> + <% include("/elements/header.html",'Import successful') %> + <!-- XXX redirect to batch search like the payment entry... --> + <% include("/elements/footer.html",'Import successful') %> +% +% } +% + + diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi new file mode 100755 index 000000000..fd3d8bb13 --- /dev/null +++ b/httemplate/misc/process/link.cgi @@ -0,0 +1,78 @@ +% +% +%my $DEBUG = 0; +% +%$cgi->param('pkgnum') =~ /^(\d+)$/; +%my $pkgnum = $1; +%$cgi->param('svcpart') =~ /^(\d+)$/; +%my $svcpart = $1; +%$cgi->param('svcnum') =~ /^(\d*)$/; +%my $svcnum = $1; +% +%unless ( $svcnum ) { +% my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart}); +% my $svcdb = $part_svc->getfield('svcdb'); +% $cgi->param('link_field') =~ /^(\w+)$/; +% my $link_field = $1; +% my %search = ( $link_field => $cgi->param('link_value') ); +% if ( $cgi->param('link_field2') =~ /^(\w+)$/ ) { +% $search{$1} = $cgi->param('link_value2'); +% } +% +% my @svc_x = ( sort { ($a->cust_svc->pkgnum > 0) <=> ($b->cust_svc->pkgnum > 0) +% or ($b->cust_svc->svcpart == $svcpart) +% <=> ($a->cust_svc->svcpart == $svcpart) +% } +% qsearch( $svcdb, \%search ) +% ); +% +% if ( $DEBUG ) { +% warn scalar(@svc_x). " candidate accounts found for linking ". +% "(svcpart $svcpart):\n"; +% foreach my $svc_x ( @svc_x ) { +% warn " ". $svc_x->email. +% " (svcnum ". $svc_x->svcnum. ",". +% " pkgnum ". $svc_x->cust_svc->pkgnum. ",". +% " svcpart ". $svc_x->cust_svc->svcpart. ")\n"; +% } +% } +% +% my $svc_x = $svc_x[0]; +% +% eidiot("$link_field not found!") unless $svc_x; +% +% $svcnum = $svc_x->svcnum; +% +%} +% +%my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "svcnum not found!" unless $old; +%my $conf = new FS::Conf; +%my($error, $new); +%if ( $old->pkgnum && ! $conf->exists('legacy_link-steal') ) { +% $error = "svcnum $svcnum already linked to package ". $old->pkgnum; +%} else { +% $new = new FS::cust_svc ({ +% 'svcnum' => $svcnum, +% 'pkgnum' => $pkgnum, +% 'svcpart' => $svcpart, +% }); +% +% $error = $new->replace($old); +%} +% +%unless ($error) { +% #no errors, so let's view this customer. +% my $custnum = $new->cust_pkg->custnum; +% print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum". +% "#cust_pkg$pkgnum" ); +%} else { +% + +<!-- mason kludge --> +% +% idiot($error); +%} +% +% + diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi new file mode 100644 index 000000000..5a97d1160 --- /dev/null +++ b/httemplate/misc/process/meta-import.cgi @@ -0,0 +1,185 @@ +<!-- mason kludge --> +<% include("/elements/header.html",'Map tables') %> + +<SCRIPT> +var gSafeOnload = new Array(); +var gSafeOnsubmit = new Array(); +window.onload = SafeOnload; +function SafeAddOnLoad(f) { + gSafeOnload[gSafeOnload.length] = f; +} +function SafeOnload() { + for (var i=0;i<gSafeOnload.length;i++) + gSafeOnload[i](); +} +function SafeAddOnSubmit(f) { + gSafeOnsubmit[gSafeOnsubmit.length] = f; +} +function SafeOnsubmit() { + for (var i=0;i<gSafeOnsubmit.length;i++) + gSafeOnsubmit[i](); +} +</SCRIPT> + +<FORM NAME="OneTrueForm" METHOD="POST" ACTION="meta-import.cgi"> +% +% #use DBIx::DBSchema; +% my $schema = new_native DBIx::DBSchema +% map { $cgi->param($_) } qw( data_source username password ); +% foreach my $field (qw( data_source username password )) { + + <INPUT TYPE="hidden" NAME=<% $field %> VALUE="<% $cgi->param($field) %>"> +% } +% +% my %schema; +% use Tie::DxHash; +% tie %schema, 'Tie::DxHash'; +% if ( $cgi->param('schema') ) { +% my $schema_string = $cgi->param('schema'); +% + <INPUT TYPE="hidden" NAME="schema" VALUE="<%$schema_string%>"> +% +% %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ +% or die "guru meditation #420: $_"; +% ( $1 => $2 ); +% } +% split( /\n/, $schema_string ); +% } +% +% #first page +% unless ( $cgi->param('magic') ) { + + + <INPUT TYPE="hidden" NAME="magic" VALUE="process"> + <% hashmaker('schema', [ $schema->tables ], + [ grep !/^h_/, dbdef->tables ], ) %> + <br><INPUT TYPE="submit" VALUE="done"> +% +% +% #second page +% } elsif ( $cgi->param('magic') eq 'process' ) { + + + <INPUT TYPE="hidden" NAME="magic" VALUE="process2"> +% +% +% my %unique; +% foreach my $table ( keys %schema ) { +% +% my @from_columns = $schema->table($table)->columns; +% my @fs_columns = dbdef->table($schema{$table})->columns; +% +% + + <% hashmaker( $table.'__'.$unique{$table}++, + \@from_columns => \@fs_columns, + $table => $schema{$table}, ) %> + <br><hr><br> +% +% +% } +% +% + + <br><INPUT TYPE="submit" VALUE="done"> +% +% +% #third (results) +% } elsif ( $cgi->param('magic') eq 'process2' ) { +% +% print "<pre>\n"; +% +% my %unique; +% foreach my $table ( keys %schema ) { +% ( my $spaces = $table ) =~ s/./ /g; +% print "'$table' => { 'table' => '$schema{$table}',\n". +% #(length($table) x ' '). " 'map' => {\n"; +% "$spaces 'map' => {\n"; +% my %map = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/ +% or die "guru meditation #420: $_"; +% ( $1 => $2 ); +% } +% split( /\n/, $cgi->param($table.'__'.$unique{$table}++) ); +% foreach ( keys %map ) { +% print "$spaces '$_' => '$map{$_}',\n"; +% } +% print "$spaces },\n"; +% print "$spaces },\n"; +% +% } +% print "\n</pre>"; +% +% } else { +% warn "unrecognized magic: ". $cgi->param('magic'); +% } +% +% + +</FORM> +</BODY> +</HTML> +% +% #hashmaker widget +% sub hashmaker { +% my($name, $from, $to, $labelfrom, $labelto) = @_; +% 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 ). +% "</SELECT>\n<BR>". +% qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_from()">!. +% '</TD><TD>'. +% qq!<SELECT NAME="${name}_to" SIZE=$tosize>\n!. +% join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$to ). +% "</SELECT>\n<BR>". +% qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_to()">!. +% '</TD></TR>'. +% '<TR><TD COLSPAN=2>'. +% qq!<INPUT TYPE="button" VALUE="map" onClick="toke_$name(this.form)">!. +% '</TD></TR><TR><TD COLSPAN=2>'. +% qq!<TEXTAREA NAME="$name" COLS=80 ROWS=8></TEXTAREA>!. +% '</TD></TR></TABLE>'. +% "<script> +% function toke_$name() { +% fromObject = document.OneTrueForm.${name}_from; +% for (var i=fromObject.options.length-1;i>-1;i--) { +% if (fromObject.options[i].selected) +% fromname = deleteOption_$name(fromObject,i); +% } +% toObject = document.OneTrueForm.${name}_to; +% for (var i=toObject.options.length-1;i>-1;i--) { +% if (toObject.options[i].selected) +% toname = deleteOption_$name(toObject,i); +% } +% document.OneTrueForm.$name.value = document.OneTrueForm.$name.value + fromname + ' => ' + toname + '\\n'; +% } +% function deleteOption_$name(object,index) { +% value = object.options[index].value; +% object.options[index] = null; +% return value; +% } +% function repack_${name}_from() { +% var object = document.OneTrueForm.${name}_from; +% object.options.length = 0; +% ". join("\n", +% map { "addOption_$name(object, '$_');\n" } +% ( sort { $a cmp $b } @$from ) ). " +% } +% function repack_${name}_to() { +% var object = document.OneTrueForm.${name}_to; +% object.options.length = 0; +% ". join("\n", +% map { "addOption_$name(object, '$_');\n" } +% ( sort { $a cmp $b } @$to ) ). " +% } +% function addOption_$name(object,value) { +% var length = object.length; +% object.options[length] = new Option(value, value, false, false); +% } +% </script>". +% ''; +% } +% +% + diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi new file mode 100644 index 000000000..027cd502a --- /dev/null +++ b/httemplate/misc/process/payment.cgi @@ -0,0 +1,144 @@ +% +%#some false laziness w/MyAccount::process_payment +% +%$cgi->param('custnum') =~ /^(\d+)$/ +% or die "illegal custnum ". $cgi->param('custnum'); +%my $custnum = $1; +% +%my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +%die "unknown custnum $custnum" unless $cust_main; +% +%$cgi->param('amount') =~ /^\s*(\d*(\.\d\d)?)\s*$/ +% or eidiot "illegal amount ". $cgi->param('amount'); +%my $amount = $1; +%eidiot "amount <= 0" unless $amount > 0; +% +%$cgi->param('year') =~ /^(\d+)$/ +% or die "illegal year ". $cgi->param('year'); +%my $year = $1; +% +%$cgi->param('month') =~ /^(\d+)$/ +% or die "illegal month ". $cgi->param('month'); +%my $month = $1; +% +%$cgi->param('payby') =~ /^(CARD|CHEK)$/ +% or die "illegal payby ". $cgi->param('payby'); +%my $payby = $1; +%my %payby2fields = ( +% 'CARD' => [ qw( address1 address2 city state zip ) ], +% 'CHEK' => [ qw( ss ) ], +%); +%my %type = ( 'CARD' => 'credit card', +% 'CHEK' => 'electronic check (ACH)', +% ); +% +%$cgi->param('payname') =~ /^([\w \,\.\-\']+)$/ +% or eidiot gettext('illegal_name'). " payname: ". $cgi->param('payname'); +%my $payname = $1; +% +%$cgi->param('paybatch') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot gettext('illegal_text'). " paybatch: ". $cgi->param('paybatch'); +%my $paybatch = $1; +% +%my $payinfo; +%my $paycvv = ''; +%if ( $payby eq 'CHEK' ) { +% +% $cgi->param('payinfo1') =~ /^(\d+)$/ +% or eidiot "illegal account number ". $cgi->param('payinfo1'); +% my $payinfo1 = $1; +% $cgi->param('payinfo2') =~ /^(\d+)$/ +% or eidiot "illegal ABA/routing number ". $cgi->param('payinfo2'); +% my $payinfo2 = $1; +% $payinfo = $payinfo1. '@'. $payinfo2; +% +%} elsif ( $payby eq 'CARD' ) { +% +% $payinfo = $cgi->param('payinfo'); +% $payinfo =~ s/\D//g; +% $payinfo =~ /^(\d{13,16})$/ +% or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; +% $payinfo = $1; +% validate($payinfo) +% or eidiot gettext('invalid_card'); # . ": ". $self->payinfo; +% eidiot gettext('unknown_card_type') +% if cardtype($payinfo) eq "Unknown"; +% +% if ( defined $cust_main->dbdef_table->column('paycvv') ) { +% if ( length($cgi->param('paycvv') ) ) { +% if ( cardtype($payinfo) eq 'American Express card' ) { +% $cgi->param('paycvv') =~ /^(\d{4})$/ +% or eidiot "CVV2 (CID) for American Express cards is four digits."; +% $paycvv = $1; +% } else { +% $cgi->param('paycvv') =~ /^(\d{3})$/ +% or eidiot "CVV2 (CVC2/CID) is three digits."; +% $paycvv = $1; +% } +% } +% } +% +%} else { +% die "unknown payby $payby"; +%} +% +%my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount, +% 'quiet' => 1, +% 'payinfo' => $payinfo, +% 'paydate' => "$year-$month-01", +% 'payname' => $payname, +% 'paybatch' => $paybatch, +% 'paycvv' => $paycvv, +% map { $_ => $cgi->param($_) } @{$payby2fields{$payby}} +%); +%eidiot($error) if $error; +% +%$cust_main->apply_payments; +% +%if ( $cgi->param('save') ) { +% my $new = new FS::cust_main { $cust_main->hash }; +% if ( $payby eq 'CARD' ) { +% $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) ); +% } elsif ( $payby eq 'CHEK' ) { +% $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) ); +% } else { +% die "unknown payby $payby"; +% } +% $new->set( 'payinfo' => $payinfo ); +% $new->set( 'paydate' => "$year-$month-01" ); +% $new->set( 'payname' => $payname ); +% +% #false laziness w/FS:;cust_main::realtime_bop - check both to make sure +% # working correctly +% my $conf = new FS::Conf; +% if ( $payby eq 'CARD' && +% grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) { +% $new->set( 'paycvv' => $paycvv ); +% } else { +% $new->set( 'paycvv' => ''); +% } +% +% $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; +% +% my $error = $new->replace($cust_main); +% eidiot "payment processed successfully, but error saving info: $error" +% if $error; +% $cust_main = $new; +%} +% +%#success! +% +% + +<% include( '/elements/header.html', ucfirst($type{$payby}). ' processing successful', + include('/elements/menubar.html', + 'Main menu' => popurl(3), + "View this customer (#$custnum)" => + popurl(3). "view/cust_main.cgi?$custnum", + ), + + ) +%> +<% include( '/elements/small_custview.html', $cust_main ) %> +</BODY> +</HTML> diff --git a/httemplate/misc/queue.cgi b/httemplate/misc/queue.cgi new file mode 100644 index 000000000..7370aabe1 --- /dev/null +++ b/httemplate/misc/queue.cgi @@ -0,0 +1,48 @@ +% +% +%$cgi->param('action') =~ /^(new|del|(retry|remove) selected)$/ +% or die "Illegal action"; +%my $action = $1; +% +%my $job; +%if ( $action eq 'new' || $action eq 'del' ) { +% $cgi->param('jobnum') =~ /^(\d+)$/ or die "Illegal jobnum"; +% my $jobnum = $1; +% $job = qsearchs('queue', { 'jobnum' => $1 }) +% or die "unknown jobnum $jobnum - ". +% "it probably completed normally or was removed by another user"; +%} +% +%if ( $action eq 'new' ) { +% my %hash = $job->hash; +% $hash{'status'} = 'new'; +% $hash{'statustext'} = ''; +% my $new = new FS::queue \%hash; +% my $error = $new->replace($job); +% die $error if $error; +%} elsif ( $action eq 'del' ) { +% my $error = $job->delete; +% die $error if $error; +%} elsif ( $action =~ /^(retry|remove) selected$/ ) { +% foreach my $jobnum ( +% map { /^jobnum(\d+)$/; $1; } grep /^jobnum\d+$/, $cgi->param +% ) { +% my $job = qsearchs('queue', { 'jobnum' => $jobnum }); +% if ( $action eq 'retry selected' && $job ) { #new +% my %hash = $job->hash; +% $hash{'status'} = 'new'; +% $hash{'statustext'} = ''; +% my $new = new FS::queue \%hash; +% my $error = $new->replace($job); +% die $error if $error; +% } elsif ( $action eq 'remove selected' && $job ) { #del +% my $error = $job->delete; +% die $error if $error; +% } +% } +%} +% +%print $cgi->redirect(popurl(2). "search/queue.html"); +% +% + diff --git a/httemplate/misc/states.cgi b/httemplate/misc/states.cgi new file mode 100644 index 000000000..cf2b46ee2 --- /dev/null +++ b/httemplate/misc/states.cgi @@ -0,0 +1,7 @@ +% +% +% my $country = $cgi->param('arg'); +% my @output = states_hash($country); +% +% +[ <% join(', ', map { qq("$_") } @output) %> ] diff --git a/httemplate/misc/susp_pkg.cgi b/httemplate/misc/susp_pkg.cgi new file mode 100755 index 000000000..ea9edc7bb --- /dev/null +++ b/httemplate/misc/susp_pkg.cgi @@ -0,0 +1,16 @@ +% +% +%#untaint pkgnum +%my ($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +%my $pkgnum = $1; +% +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% +%my $error = $cust_pkg->suspend; +%&eidiot($error) if $error; +% +%print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); +% +% + diff --git a/httemplate/misc/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi new file mode 100755 index 000000000..56a3ff854 --- /dev/null +++ b/httemplate/misc/unapply-cust_credit.cgi @@ -0,0 +1,19 @@ +% +% +%#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; +% +%foreach my $cust_credit_bill ( $cust_credit->cust_credit_bill ) { +% my $error = $cust_credit_bill->delete; +% eidiot($error) if $error; +%} +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi new file mode 100755 index 000000000..b28f61b0f --- /dev/null +++ b/httemplate/misc/unapply-cust_pay.cgi @@ -0,0 +1,19 @@ +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); +%my $custnum = $cust_pay->custnum; +% +%foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) { +% my $error = $cust_bill_pay->delete; +% eidiot($error) if $error; +%} +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi new file mode 100755 index 000000000..e42feda8a --- /dev/null +++ b/httemplate/misc/unprovision.cgi @@ -0,0 +1,31 @@ +% +% +%my $dbh = dbh; +% +%#untaint svcnum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +% +%#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +%#die "Unknown svcnum!" unless $svc_acct; +% +%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +%die "Unknown svcnum!" unless $cust_svc; +% +%my $custnum = $cust_svc->cust_pkg->custnum; +% +%my $error = $cust_svc->cancel; +% +%if ( $error ) { +% + +<!-- mason kludge --> +% +% &eidiot($error); +%} else { +% print $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum"); +%} +% +% + diff --git a/httemplate/misc/unsusp_pkg.cgi b/httemplate/misc/unsusp_pkg.cgi new file mode 100755 index 000000000..79c07a72a --- /dev/null +++ b/httemplate/misc/unsusp_pkg.cgi @@ -0,0 +1,16 @@ +% +% +%#untaint pkgnum +%my ($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +%my $pkgnum = $1; +% +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +% +%my $error = $cust_pkg->unsuspend; +%&eidiot($error) if $error; +% +%print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); +% +% + diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi new file mode 100755 index 000000000..75c3edc06 --- /dev/null +++ b/httemplate/misc/unvoid-cust_pay_void.cgi @@ -0,0 +1,17 @@ +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay_void = qsearchs('cust_pay_void', { 'paynum' => $paynum } ); +%my $custnum = $cust_pay_void->custnum; +% +%my $error = $cust_pay_void->unvoid; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi new file mode 100644 index 000000000..742bbc0c6 --- /dev/null +++ b/httemplate/misc/upload-batch.cgi @@ -0,0 +1,34 @@ +% +% +% my $fh = $cgi->upload('batch_results'); +% my $filename = $cgi->param('batch_results'); +% $filename =~ /^(.*[\/\\])?([^\/\\]+)$/ +% or die "unparsable filename: $filename\n"; +% my $paybatch = $2; +% +% 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 --> + <% include("/elements/header.html",'Batch results upload successful') %> +% +% } +% + + diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi new file mode 100755 index 000000000..b55d22c41 --- /dev/null +++ b/httemplate/misc/void-cust_pay.cgi @@ -0,0 +1,17 @@ +% +% +%#untaint paynum +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/ || die "Illegal paynum"; +%my $paynum = $1; +% +%my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); +%my $custnum = $cust_pay->custnum; +% +%my $error = $cust_pay->void; +%eidiot($error) if $error; +% +%print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); +% +% + diff --git a/httemplate/misc/whois.cgi b/httemplate/misc/whois.cgi new file mode 100644 index 000000000..d3d9649fd --- /dev/null +++ b/httemplate/misc/whois.cgi @@ -0,0 +1,27 @@ +% +% my $svcnum = $cgi->param('svcnum'); +% my $custnum = $cgi->param('custnum'); +% my $domain = $cgi->param('domain'); +% +% + +<% include("/elements/header.html","Whois $domain", menubar( + ( $custnum + ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + ) + : () + ), + "View this domain (#$svcnum)" => "${p}view/svc_domain.cgi?$svcnum", + "Main menu" => $p, +)) %> +% my $whois = eval { whois($domain) }; +% if ( $@ ) { +% ( $whois = $@ ) =~ s/ at \/.*Net\/Whois\/Raw\.pm line \d+.*$//s; +% } else { +% $whois =~ s/^\n+//; +% } +% + +<PRE><% $whois %></PRE> +</BODY> +</HTML> diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi new file mode 100644 index 000000000..67512fad9 --- /dev/null +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -0,0 +1,22 @@ +% +% my $sub = $cgi->param('sub'); +% +% if ( $sub eq 'custnum_search' ) { +% +% my $custnum = $cgi->param('arg'); +% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +% +% +"<% $cust_main ? $cust_main->name : '' %>" +% } elsif ( $sub eq 'smart_search' ) { +% +% my $string = $cgi->param('arg'); +% my @cust_main = smart_search( 'search' => $string ); +% my $return = [ map [ $_->custnum, $_->name ], @cust_main ]; +% +% +<% objToJson($return) %> +% } + + + diff --git a/httemplate/misc/xmlrpc.cgi b/httemplate/misc/xmlrpc.cgi new file mode 100644 index 000000000..1d0383f2a --- /dev/null +++ b/httemplate/misc/xmlrpc.cgi @@ -0,0 +1,18 @@ +% +% +% my $request_xml = $cgi->param('POSTDATA'); +% +% #$r->log_error($request_xml); +% +% my $fsxmlrpc = new FS::XMLRPC; +% my ($error, $response_xml) = $fsxmlrpc->serve($request_xml); +% +% #$r->log_error($error) if $error; +% +% http_header('Content-Type' => 'text/xml', +% 'Content-Length' => length($response_xml)); +% +% print $response_xml; +% +% + diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html new file mode 100644 index 000000000..827a50895 --- /dev/null +++ b/httemplate/search/cdr.html @@ -0,0 +1,38 @@ +% +% +%my $title = 'Call Detail Records'; +%my $hashref = {}; +%my $count_query = 'SELECT COUNT(*) FROM cdr'; +% +%#process params for CDR search, populate $hashref... +%# and fixup $count_query +% +%if ( $cgi->param('freesidestatus') eq 'NULL' ) { +% +% my $title = "Unprocessed $title"; +% $hashref->{'freesidestatus'} = ''; # Record.pm will take care of it +% #$count_query .= " AND ( freesidestatus IS NULL OR freesidestatus = '' )"; +% $count_query .= " WHERE ( freesidestatus IS NULL OR freesidestatus = '' )"; +% +%} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) { +% +% my $title = "Processed $title"; +% $hashref->{'freesidestatus'} = $1; +% #$count_query .= " AND freesidestatus = '$1'"; +% $count_query .= " WHERE freesidestatus = '$1'"; +% +%} +% +% +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'call detail records', + 'query' => { 'table' => 'cdr', + 'hashref' => $hashref + }, + 'count_query' => $count_query, + 'header' => [ fields('cdr') ], #XXX fill in some nice names + 'fields' => [ fields('cdr') ], #XXX fill in some pretty-print + # processing, etc. + ) +%> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html new file mode 100755 index 000000000..894ddad51 --- /dev/null +++ b/httemplate/search/cust_bill.html @@ -0,0 +1,196 @@ +% +% +% my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )'; +% #here is the agent virtualization +% my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% my( $count_query, $sql_query ); +% my( $count_addl ) = ( '' ); +% my( $distinct ) = ( '' ); +% my($begin, $end) = ( '', '' ); +% my($agentnum) = ( '' ); +% my($open, $days) = ( '', '' ); +% if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { +% $count_query = +% "SELECT COUNT(*) FROM cust_bill $join_cust_main". +% " WHERE invnum = $2 AND $agentnums_sql"; #agent virtualization +% $sql_query = { +% 'table' => 'cust_bill', +% 'addl_from' => $join_cust_main, +% 'hashref' => { 'invnum' => $2 }, +% #'select' => '*', +% 'extra_sql' => " AND $agentnums_sql", #agent virtualization +% }; +% } else { +% #if ( $cgi->param('begin') || $cgi->param('end') +% # || $cgi->param('beginning') || $cgi->param('ending') +% # || $cgi->keywords +% # ) +% #{ +% +% #some false laziness w/cust_bill::re_X +% my @where; +% my $orderby = 'ORDER BY cust_bill._date'; +% +% if ( $cgi->param('beginning') +% && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { +% $begin = str2time($1); +% push @where, "cust_bill._date >= $begin"; +% } +% if ( $cgi->param('ending') +% && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { +% $end = str2time($1) + 86399; +% push @where, "cust_bill._date < $end"; +% } +% +% if ( $cgi->param('begin') =~ /^(\d+)$/ ) { +% $begin = $1; +% push @where, "cust_bill._date >= $begin"; +% } +% if ( $cgi->param('end') =~ /^(\d+)$/ ) { +% $end = $1; +% push @where, "cust_bill._date < $end"; +% } +% +% if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) { +% push @where, "cust_bill.invnum >= $1"; +% } +% if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) { +% push @where, "cust_bill.invnum <= $1"; +% } +% +% if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agentnum = $1; +% push @where, "cust_main.agentnum = $agentnum"; +% } +% +% my $owed = +% "charged - ( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay +% WHERE cust_bill_pay.invnum = cust_bill.invnum ) +% - ( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill +% WHERE cust_credit_bill.invnum = cust_bill.invnum )"; +% +% if ( $cgi->param('open') ) { +% push @where, "0 != $owed"; +% $open = 1; +% } +% +% my($query) = $cgi->keywords; +% if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { +% ($open, $days, my $field) = ($1, $2, $3); +% $field = "_date" if $field eq 'date'; +% $orderby = "ORDER BY cust_bill.$field"; +% push @where, "0 != $owed" if $open; +% push @where, "cust_bill._date < ". (time-86400*$days) if $days; +% } +% +% #here is the agent virtualization +% push @where, $agentnums_sql; +% my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; +% +% if ( $cgi->param('newest_percust') ) { +% $distinct = 'DISTINCT ON ( cust_bill.custnum )'; +% $orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC'; +% #$count_query = "SELECT 'N/A', 'N/A', 'N/A'"; #XXXXXXX fix +% $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; +% } +% +% unless ( $count_query ) { +% $count_query = "SELECT COUNT(*), sum(charged), sum($owed)"; +% $count_addl = [ '$%.2f total invoiced', +% '$%.2f total outstanding balance', +% ]; +% } +% $count_query .= " FROM cust_bill $join_cust_main $extra_sql"; +% +% $sql_query = { +% 'table' => 'cust_bill', +% 'addl_from' => $join_cust_main, +% 'hashref' => {}, +% 'select' => "$distinct ". join(', ', +% 'cust_bill.*', +% #( map "cust_main.$_", qw(custnum last first company) ), +% 'cust_main.custnum as cust_main_custnum', +% FS::UI::Web::cust_sql_fields(), +% "$owed as owed", +% ), +% 'extra_sql' => "$extra_sql $orderby" +% }; +% +% } +% +% my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ]; +% my $clink = sub { +% my $cust_bill = shift; +% $cust_bill->cust_main_custnum +% ? [ "${p}view/cust_main.cgi?", 'custnum' ] +% : ''; +% }; +% +% my $conf = new FS::Conf; +% my $money_char = $conf->config('money_char') || '$'; +% +% my $html_init = join("\n", map { +% ( my $action = $_ ) =~ s/_$//; +% include('/elements/progress-init.html', +% $_.'form', +% [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], +% "../misc/${_}invoices.cgi", +% { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... +% $_, #key +% ), +% qq!<FORM NAME="${_}form">!, +% qq!<INPUT TYPE="hidden" NAME="begin" VALUE="$begin">!, +% qq!<INPUT TYPE="hidden" NAME="end" VALUE="$end">!, +% qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!, +% qq!<INPUT TYPE="hidden" NAME="open" VALUE="$open">!, +% qq!<INPUT TYPE="hidden" NAME="days" VALUE="$days">!, +% qq!</FORM>! +% } qw( print_ email_ fax_ ) ); +% +% my $menubar = [ +% 'Main menu' => $p, +% 'Print these invoices' => +% "javascript:print_process()", +% 'Email these invoices' => +% "javascript:email_process()", +% ]; +% +% push @$menubar, 'Fax these invoices' => +% "javascript:fax_process()" +% if $conf->exists('hylafax'); +% +% +<% include( 'elements/search.html', + 'title' => 'Invoice Search Results', + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'invoices', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => $count_addl, + 'redirect' => $link, + 'header' => [ 'Invoice #', + 'Balance', + 'Amount', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'invnum', + sub { sprintf($money_char.'%.2f', shift->get('owed') ) }, + sub { sprintf($money_char.'%.2f', shift->charged ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'rrrrll', + 'links' => [ + $link, + $link, + $link, + $link, + ( map { $clink } FS::UI::Web::cust_header() ), + ], + + ) +%> diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi new file mode 100644 index 000000000..7c31c257d --- /dev/null +++ b/httemplate/search/cust_bill_event.cgi @@ -0,0 +1,141 @@ +<%init> + +my $title = $cgi->param('failed') + ? 'Failed invoice events' + : 'Invoice events'; + +my @search = (); + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + #my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + #die "unknown agentnum $1" unless $agent; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "cust_bill_event._date >= $beginning", + "cust_bill_event._date <= $ending"; + +if ( $cgi->param('failed') ) { + push @search, "statustext != ''", + "statustext IS NOT NULL", + "statustext != 'N/A'"; +} + +if ( $cgi->param('part_bill_event.payby') =~ /^(\w+)$/ ) { + push @search, "part_bill_event.payby = '$1'"; +} + +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search ); + +my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '. + 'LEFT JOIN cust_bill USING ( invnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) '; + +my $sql_query = { + 'table' => 'cust_bill_event', + 'select' => join(', ', + 'cust_bill_event.*', + 'part_bill_event.event', + 'cust_bill.custnum', + 'cust_bill._date AS cust_bill_date', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$where ORDER BY _date ASC", + 'addl_from' => $join, +}; + +my $count_sql = "SELECT COUNT(*) FROM cust_bill_event $join $where"; + +my $conf = new FS::Conf; + +my $failed = $cgi->param('failed'); + +my $html_init = join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ 'action', 'beginning', 'ending', 'failed' ], + "../misc/${_}invoice_events.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $_, #key + ), + qq!<FORM NAME="${_}form">!, + qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though + qq!<INPUT TYPE="hidden" NAME="beginning" VALUE="$beginning">!, + qq!<INPUT TYPE="hidden" NAME="ending" VALUE="$ending">!, + qq!<INPUT TYPE="hidden" NAME="failed" VALUE="$failed">!, + qq!</FORM>! +} qw( print_ email_ fax_ ) ); + +my $menubar = [ + 'Re-print these events' => + "javascript:print_process()", + 'Re-email these events' => + "javascript:email_process()", + ]; + +push @$menubar, 'Re-fax these events' => + "javascript:fax_process()" + if $conf->exists('hylafax'); + +my $link_cust = sub { + my $cust_bill_event = shift; + $cust_bill_event->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +</%init> +<% include( 'elements/search.html', + 'title' => $title, + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'billing events', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Event', + 'Date', + 'Status', + #'Inv #', 'Inv Date', 'Cust #', + 'Invoice', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'event', + sub { time2str("%b %d %Y %T", $_[0]->_date) }, + sub { + #my $cust_bill_event = shift; + my $status = $_[0]->status; + $status .= ': '.$_[0]->statustext + if $_[0]->statustext; + $status; + }, + sub { + #my $cust_bill_event = shift; + 'Invoice #'. $_[0]->invnum. + ' ('. + time2str("%D", $_[0]->cust_bill_date). + ')'; + }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + sub { + my $part_bill_event = shift; + my $template = $part_bill_event->templatename; + $template .= '-' if $template; + [ "${p}view/cust_bill.cgi?$template", 'invnum']; + }, + ( map { $link_cust } FS::UI::Web::cust_header() ), + ], + ) +%> diff --git a/httemplate/search/cust_bill_event.html b/httemplate/search/cust_bill_event.html new file mode 100755 index 000000000..2aa2ccc6c --- /dev/null +++ b/httemplate/search/cust_bill_event.html @@ -0,0 +1,58 @@ +<% include( + '/elements/header.html', + ( $cgi->param('failed') ? 'Failed invoice events' : 'Invoice events' ), + ) +%> + + <FORM ACTION="cust_bill_event.cgi" METHOD="GET"> + <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') %>"> + <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <!--<TR> + <TD ALIGN="right">Customer type</TD> + <TD><SELECT MULTIPLE NAME="perhaps_payby"> + <OPTION SELECTED VALUE="CARD">Credit card (automatic) + <OPTION SELECTED VALUE="CHEK">E-check (automatic) + <OPTION SELECTED VALUE="LECB">Phone bill billing + <OPTION SELECTED VALUE="BILL">Billing + <OPTION SELECTED VALUE="DCRD">Credit card (on-demand) + <OPTION SELECTED VALUE="DCHK">E-check (on-demand) + </TD> + </TR> + --> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <!-- + <TR> + <TD ALIGN="right">Events: </TD> + <TD> + <SELECT NAME="eventpart"> + <OPTION SELECTED VALUE=""><% $cgi->param('failed') ? '(all failed events)' : '(all events)' %> +% #foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) { +% #} + + </SELECT> + </TD> + </TR> + --> + <TR> + <TD ALIGN="right">Events for payment type: </TD> + <TD> + <SELECT NAME="part_bill_event.payby"> + <OPTION SELECTED VALUE="">(all) + <OPTION VALUE="CARD">Credit card (automatic) + <OPTION VALUE="BILL">Billing + <OPTION VALUE="CHEK">Electronic check (automatic) + <OPTION VALUE="DCRD">Credit card (on-demand) + <OPTION VALUE="DCHK">Electronic check (on-demand) + <OPTION VALUE="LECB">Phone bill billing + <OPTION VALUE="COMP">Complimentary + </SELECT> + </TD> + </TR> + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi new file mode 100644 index 000000000..b5289d713 --- /dev/null +++ b/httemplate/search/cust_bill_pkg.cgi @@ -0,0 +1,169 @@ +% +% +%my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +% +%my $join_cust = " +% JOIN cust_bill USING ( invnum ) +% LEFT JOIN cust_main USING ( custnum ) +%"; +% +%my $join_pkg = " +% LEFT JOIN cust_pkg USING ( pkgnum ) +% LEFT JOIN part_pkg USING ( pkgpart ) +%"; +% +%my $where = " WHERE _date >= $beginning AND _date <= $ending "; +% +%$where .= " AND payby != 'COMP' " +% unless $cgi->param('include_comp_cust'); +% +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $where .= " AND agentnum = $1 "; +%} +% +%if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { +% if ( $1 == 0 ) { +% $where .= " AND classnum IS NULL "; +% } else { +% $where .= " AND classnum = $1 "; +% } +%} +% +%if ( $cgi->param('out') ) { +% +% $where .= " +% AND 0 = ( +% SELECT COUNT(*) FROM cust_main_county +% WHERE ( cust_main_county.county = cust_main.county +% OR ( cust_main_county.county IS NULL AND cust_main.county = '' ) +% OR ( cust_main_county.county = '' AND cust_main.county IS NULL) +% OR ( cust_main_county.county IS NULL AND cust_main.county IS NULL) +% ) +% AND ( cust_main_county.state = cust_main.state +% OR ( cust_main_county.state IS NULL AND cust_main.state = '' ) +% OR ( cust_main_county.state = '' AND cust_main.state IS NULL ) +% OR ( cust_main_county.state IS NULL AND cust_main.state IS NULL ) +% ) +% AND cust_main_county.country = cust_main.country +% AND cust_main_county.tax > 0 +% ) +% "; +% +%} elsif ( $cgi->param('country' ) ) { +% +% my $county = dbh->quote( $cgi->param('county') ); +% my $state = dbh->quote( $cgi->param('state') ); +% my $country = dbh->quote( $cgi->param('country') ); +% $where .= " +% AND ( county = $county OR $county = '' ) +% AND ( state = $state OR $state = '' ) +% AND country = $country +% "; +% $where .= ' AND taxclass = '. dbh->quote( $cgi->param('taxclass') ) +% if $cgi->param('taxclass'); +% +%} +% +%$where .= ' AND pkgnum != 0' if $cgi->param('nottax'); +% +%$where .= ' AND pkgnum = 0' if $cgi->param('istax'); +% +%$where .= " AND tax = 'Y'" if $cgi->param('cust_tax'); +% +%my $count_query; +%if ( $cgi->param('pkg_tax') ) { +% +% $count_query = +% "SELECT COUNT(*), SUM( +% ( CASE WHEN part_pkg.setuptax = 'Y' +% THEN cust_bill_pkg.setup +% ELSE 0 +% END +% ) +% + +% ( CASE WHEN part_pkg.recurtax = 'Y' +% THEN cust_bill_pkg.recur +% ELSE 0 +% END +% ) +% ) +% "; +% +% $where .= " AND ( +% ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) +% OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) +% ) +% AND ( tax != 'Y' OR tax IS NULL ) +% "; +% +%} else { +% +% $count_query = +% "SELECT COUNT(*), SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)"; +% +%} +%$count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where"; +% +%my $query = { +% 'table' => 'cust_bill_pkg', +% 'addl_from' => "$join_cust $join_pkg", +% 'hashref' => {}, +% 'select' => join(', ', +% 'cust_bill_pkg.*', +% 'cust_bill._date', +% 'part_pkg.pkg', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => $where, +%}; +% +%my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +%my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; +% +%my $conf = new FS::Conf; +%my $money_char = $conf->config('money_char') || '$'; +% +% +<% include( 'elements/search.html', + 'title' => 'Line items', + 'name' => 'line items', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Description', + 'Setup charge', + 'Recurring charge', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'billpkgnum', + sub { $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + }, + #strikethrough or "N/A ($amount)" or something these when + # they're not applicable to pkg_tax search + sub { sprintf($money_char.'%.2f', shift->setup ) }, + sub { sprintf($money_char.'%.2f', shift->recur ) }, + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + '', + $ilink, + $ilink, + ( map { $clink } FS::UI::Web::cust_header() ), + ], + 'align' => 'rlrrrc', + ) +%> + diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html new file mode 100755 index 000000000..80cfc4585 --- /dev/null +++ b/httemplate/search/cust_credit.html @@ -0,0 +1,99 @@ +% +% my $title = 'Credit Search Results'; +% #my( $count_query, $sql_query ); +% +% my @search = (); +% +% if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { +% push @search, "cust_credit.otaker = '$1'"; +% } +% +% if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% push @search, "agentnum = $1"; +% my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +% die "unknown agentnum $1" unless $agent; +% $title = $agent->agent. " $title"; +% } +% +% #false laziness with cust_pkg.cgi and cust_pay.cgi +% if ( $cgi->param('beginning') +% && $cgi->param('beginning') =~ /^([ 0-9\-\/]{1,10})$/ ) { +% my $beginning = str2time($1); +% push @search, "_date >= $beginning "; +% } +% if ( $cgi->param('ending') +% && $cgi->param('ending') =~ /^([ 0-9\-\/]{1,10})$/ ) { +% my $ending = str2time($1) + 86399; +% push @search, " _date <= $ending "; +% } +% +% if ( $cgi->param('begin') +% && $cgi->param('begin') =~ /^(\d+)$/ ) { +% push @search, "_date >= $1 "; +% } +% if ( $cgi->param('end') +% && $cgi->param('end') =~ /^(\d+)$/ ) { +% push @search, " _date < $1 "; +% } +% +% #here is the agent virtualization +% push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% my $where = 'WHERE '. join(' AND ', @search); +% +% my $count_query = 'SELECT COUNT(*), SUM(amount) '. +% 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '. +% $where; +% +% my $sql_query = { +% 'table' => 'cust_credit', +% 'select' => join(', ', +% 'cust_credit.*', +% 'cust_main.custnum as cust_main_custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'hashref' => {}, +% 'extra_sql' => $where, +% 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', +% }; +% +% my $clink = sub { +% my $cust_bill = shift; +% $cust_bill->cust_main_custnum +% ? [ "${p}view/cust_main.cgi?", 'custnum' ] +% : ''; +% }; +% +% +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'credits', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total credited', ], + #'redirect' => $link, + 'header' => [ 'Amount', + 'Date', + FS::UI::Web::cust_header(), + 'By', + 'Reason' + ], + 'fields' => [ + #'crednum', + sub { sprintf('$%.2f', shift->amount ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + 'otaker', + 'reason', + ], + #'align' => 'rrrllll', + 'align' => 'rr', + 'links' => [ + '', + '', + ( map { $clink } FS::UI::Web::cust_header() ), + '', + '', + ], + ) +%> diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi new file mode 100755 index 000000000..210172fc0 --- /dev/null +++ b/httemplate/search/cust_main-otaker.cgi @@ -0,0 +1,25 @@ +<% include('/elements/header.html', 'Customer Search' ) %> + +<FORM ACTION="cust_main.cgi" METHOD="GET"> + +Search for <B>Order taker</B>: + <INPUT TYPE="hidden" NAME="otaker_on" VALUE="TRUE"> +% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") +% or die dbh->errstr; +% $sth->execute() or die $sth->errstr; +% #my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; +% + +<SELECT NAME="otaker"> +% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) { + + <OPTION><% $otaker->[0] %> +% } + +</SELECT> + +<P><INPUT TYPE="submit" VALUE="Search"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html new file mode 100644 index 000000000..9790c0fef --- /dev/null +++ b/httemplate/search/cust_main-zip.html @@ -0,0 +1,96 @@ +% +% +%# XXX link to customers +% +%my @where = (); +% +%# select status +% +%if ( $cgi->param('status') =~ /^(prospect|uncancel|active|susp|cancel)$/ ) { +% my $method = $1.'_sql'; +% push @where, FS::cust_main->$method(); +%} +% +%# select agent +%# XXX this needs to be virtualized by agent too (like lots of stuff) +% +%my $agentnum = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agentnum = $1; +% push @where, "cust_main.agentnum = $agentnum"; +%} +%my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; +% +%# bill zip vs ship zip +% +%sub fieldorempty { +% my $field = shift; +% "CASE WHEN $field IS NULL THEN '' ELSE $field END"; +%} +% +%sub strip_plus4 { +% my $field = shift; +% "CASE WHEN $field is NULL +% THEN '' +% ELSE CASE WHEN $field LIKE '_____-____' +% THEN SUBSTRING($field FROM 1 FOR 5) +% ELSE $field +% END +% END"; +%} +% +%my( $zip, $czip); +%if ( $cgi->param('column') eq 'ship_zip' ) { +% +% my $casewhen_noship = +% "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN "; +% +% $czip = "$casewhen_noship zip ELSE ship_zip END"; +% +% if ( $cgi->param('ignore_plus4') ) { +% $zip = $casewhen_noship. strip_plus4('zip'). +% " ELSE ". strip_plus4('ship_zip'). ' END'; +% +% } else { +% $zip = $casewhen_noship. fieldorempty('zip'). +% " ELSE ". fieldorempty('ship_zip'). ' END'; +% } +% +%} else { +% +% $czip = 'zip'; +% +% if ( $cgi->param('ignore_plus4') ) { +% $zip = strip_plus4('zip'); +% } else { +% $zip = fieldorempty('zip'); +% } +% +%} +% +%# construct the queries and send 'em off +% +%my $sql_query = +% "SELECT $zip AS zipcode, +% COUNT(*) AS num_cust +% FROM cust_main +% $where +% GROUP BY zipcode +% ORDER BY num_cust DESC +% "; +% +%my $count_sql = "select count(distinct $czip) from cust_main $where"; +% +%# XXX should link... +% +% +<% include( 'elements/search.html', + 'title' => 'Zip code Search Results', + 'name' => 'zip codes', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Zip code', 'Customers', ], + #'fields' => [ 'zip', 'num_cust', ], + 'links' => [ '', sub { 'somewhere'; } ], + ) +%> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi new file mode 100755 index 000000000..509fb294d --- /dev/null +++ b/httemplate/search/cust_main.cgi @@ -0,0 +1,744 @@ +% +% +%my $conf = new FS::Conf; +%my $maxrecords = $conf->config('maxsearchrecordsperpage'); +% +%#my $cache; +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join svc_acct using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join ( +%# svc_acct left outer join ( +%# select svcnum, domain, catchall from svc_domain +%# ) as svc_acct_domsvc ( +%# svc_acct_svcnum, svc_acct_domain, svc_acct_catchall +%# ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum +%# ) using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%my $limit = ''; +%$limit .= "LIMIT $maxrecords" if $maxrecords; +% +%my $offset = $cgi->param('offset') || 0; +%$limit .= " OFFSET $offset" if $offset; +% +%my $total = 0; +% +%my(@cust_main, $sortby, $orderby); +%my @select = (); +%my @addl_headers = (); +%my @addl_cols = (); +%if ( $cgi->param('browse') +% || $cgi->param('otaker_on') +% || $cgi->param('agentnum_on') +%) { +% +% my %search = (); +% +% if ( $cgi->param('browse') ) { +% my $query = $cgi->param('browse'); +% if ( $query eq 'custnum' ) { +% $sortby=\*custnum_sort; +% $orderby = "ORDER BY custnum"; +% } elsif ( $query eq 'last' ) { +% $sortby=\*last_sort; +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; +% } elsif ( $query eq 'company' ) { +% $sortby=\*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% } elsif ( $query eq 'tickets' ) { +% $sortby = \*tickets_sort; +% $orderby = "ORDER BY tickets DESC"; +% push @select, FS::TicketSystem->sql_num_customer_tickets. " as tickets"; +% push @addl_headers, 'Tickets'; +% push @addl_cols, 'tickets'; +% } else { +% die "unknown browse field $query"; +% } +% } else { +% $sortby = \*last_sort; #?? +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; #?? +% } +% +% 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..."; +% } +% +% my @qual = (); +% +% my $ncancelled = ''; +% +% if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') ) +% ) { +% #grep { $_->ncancelled_pkgs || ! $_->all_pkgs } +% push @qual, FS::cust_main->uncancel_sql; +% +% } +% +% push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled'); +% push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect'); +% push @qual, FS::cust_main->active_sql if $cgi->param('active'); +% push @qual, FS::cust_main->inactive_sql if $cgi->param('inactive'); +% push @qual, FS::cust_main->susp_sql if $cgi->param('suspended'); +% +% #EWWWWWW +% my $qual = join(' AND ', +% map { "$_ = ". dbh->quote($search{$_}) } keys %search ); +% +% my $addl_qual = join(' AND ', @qual); +% +% #here is the agent virtualization +% $addl_qual .= ( $addl_qual ? ' AND ' : '' ). +% $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% if ( $addl_qual ) { +% $qual .= ' AND ' if $qual; +% $qual .= $addl_qual; +% } +% +% $qual = " WHERE $qual" if $qual; +% my $statement = "SELECT COUNT(*) FROM cust_main $qual"; +% my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; +% $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; +% +% $total = $sth->fetchrow_arrayref->[0]; +% +% if ( $addl_qual ) { +% if ( %search ) { +% $addl_qual = " AND $addl_qual"; +% } else { +% $addl_qual = " WHERE $addl_qual"; +% } +% } +% +% my $select; +% if ( @select ) { +% $select = 'cust_main.*, '. join (', ', @select); +% } else { +% $select = '*'; +% } +% +% @cust_main = qsearch('cust_main', \%search, $select, +% "$addl_qual $orderby $limit" ); +% +%# foreach my $cust_main ( @just_cust_main ) { +%# +%# my @one_cust_main; +%# $FS::Record::DEBUG=1; +%# ( $cache, @one_cust_main ) = jsearch( +%# "$monsterjoin", +%# { 'custnum' => $cust_main->custnum }, +%# '', +%# '', +%# 'cust_main', +%# 'custnum', +%# ); +%# push @cust_main, @one_cust_main; +%# } +% +%} else { +% @cust_main=(); +% $sortby = \*last_sort; +% +% push @cust_main, @{&custnumsearch} +% if $cgi->param('custnum_on') && $cgi->param('custnum_text'); +% push @cust_main, @{&cardsearch} +% if $cgi->param('card_on') && $cgi->param('card'); +% push @cust_main, @{&lastsearch} +% if $cgi->param('last_on') && $cgi->param('last_text'); +% push @cust_main, @{&companysearch} +% if $cgi->param('company_on') && $cgi->param('company_text'); +% push @cust_main, @{&address2search} +% if $cgi->param('address2_on') && $cgi->param('address2_text'); +% push @cust_main, @{&phonesearch} +% if $cgi->param('phone_on') && $cgi->param('phone_text'); +% push @cust_main, @{&referralsearch} +% if $cgi->param('referral_custnum'); +% +% if ( $cgi->param('company_on') && $cgi->param('company_text') ) { +% $sortby = \*company_sort; +% push @cust_main, @{&companysearch}; +% } +% +% if ( $cgi->param('search_cust') ) { +% $sortby = \*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% push @cust_main, smart_search( 'search' => $cgi->param('search_cust') ); +% } +% +% @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main +% 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; +%} +% +%my %all_pkgs; +%if ( $conf->exists('hidecancelledpackages' ) ) { +% %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main; +%} else { +% %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main; +%} +%#%all_pkgs = (); +% +%if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { +% if ( $cgi->param('quickpay') eq 'yes' ) { +% print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum); +% } else { +% print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum); +% } +% #exit; +%} elsif ( scalar(@cust_main) == 0 ) { +% + +<!-- mason kludge --> +% +% eidiot "No matching customers found!\n"; +%} else { +% + +<% include('/elements/header.html', "Customer Search Results", '' ) %> +% $total ||= scalar(@cust_main); + + + <% $total %> matching customers found +% +% #begin pager +% my $pager = ''; +% if ( $total != scalar(@cust_main) && $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 +% +% 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+)$/ +% or eidiot "Illegal referral_custnum\n"; +% my $referral_custnum = $1; +% my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } ); +% print '<FORM METHOD="GET">'. +% qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!. +% 'referrals of <A HREF="'. popurl(2). +% "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ". +% ( $cust_main->company +% || $cust_main->last. ', '. $cust_main->first ). +% '</A>'; +% print "\n",<<END; +% <SCRIPT> +% function changed(what) { +% what.form.submit(); +% } +% </SCRIPT> +%END +% print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">'; +% my $max = 8; #config file +% $cgi->param('referral_depth') =~ /^(\d*)$/ +% or eidiot "Illegal referral_depth"; +% my $referral_depth = $1; +% +% foreach my $depth ( 1 .. $max ) { +% print '<OPTION', +% ' SELECTED'x($depth == $referral_depth), +% ">$depth"; +% } +% print "</SELECT> levels deep". +% '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'. +% '</FORM>'; +% } +% +% my @custom_priorities = (); +% if ( $conf->config('ticket_system-custom_priority_field') +% && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { +% @custom_priorities = +% $conf->config('ticket_system-custom_priority_field-values'); +% } +% +% print "<BR><BR>". $pager. include('/elements/table-grid.html'). <<END; +% <TR> +% <TH CLASS="grid" BGCOLOR="#cccccc"></TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">(bill) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +% +%if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">(service) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +%} +% +%foreach my $addl_header ( @addl_headers ) { +% print '<TH CLASS="grid" BGCOLOR="#cccccc">'. "$addl_header</TH>"; +%} +% +%print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">Packages</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Services</TH> +% </TR> +%END +% +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% my(%saw,$cust_main); +% foreach $cust_main ( +% sort $sortby grep(!$saw{$_->custnum}++, @cust_main) +% ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my($custnum,$last,$first,$company)=( +% $cust_main->custnum, +% $cust_main->getfield('last'), +% $cust_main->getfield('first'), +% $cust_main->company, +% ); +% +% my(@lol_cust_svc); +% my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} ); +% foreach ( @{$all_pkgs{$custnum}} ) { +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my @cust_svc = $_->cust_svc; +% push @lol_cust_svc, \@cust_svc; +% $rowspan += scalar(@cust_svc) || 1; +% } +% +% #my($rowspan) = scalar(@{$all_pkgs{$custnum}}); +% my $view; +% if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) { +% $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum; +% } else { +% $view = $p. 'view/cust_main.cgi?'. $custnum; +% } +% my $pcompany = $company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% $custnum %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$last, $first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pcompany %></TD> +% +% if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% my($ship_last,$ship_first,$ship_company)=( +% $cust_main->ship_last || $cust_main->getfield('last'), +% $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, +% $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, +% ); +% my $pship_company = $ship_company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pship_company %></A></TD> +% } +% +% foreach my $addl_col ( @addl_cols ) { +% if ( $addl_col eq 'tickets' ) { +% if ( @custom_priorities ) { + + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> +% foreach my $priority ( @custom_priorities, '' ) { +% +% my $num = +% FS::TicketSystem->num_customer_tickets($custnum,$priority); +% my $ahref = ''; +% $ahref= '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum,$priority). +% '">' +% if $num; +% + + + <TR> + <TD ALIGN=right> + <FONT SIZE=-1><% $ahref.$num %></A></FONT> + </TD> + <TD ALIGN=left> + <FONT SIZE=-1><% $ahref %><% $priority || '<i>(none)</i>' %></A></FONT> + </TD> + </TR> +% } + + + <TR> + <TH ALIGN=right STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> +% } +% +% my $ahref = ''; +% $ahref = '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum). +% '">' +% if $cust_main->get($addl_col); +% + + + <% $ahref %><% $cust_main->get($addl_col) %></A> +% if ( @custom_priorities ) { + + + </FONT></TH> + <TH ALIGN=left STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1><% ${ahref} %>Total</A><FONT> + </TH> + </TR> + </TABLE> +% } + + + </FONT></TD> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + <% $cust_main->get($addl_col) %> + </FONT></TD> +% +% } +% } +% +% my($n1)=''; +% foreach ( @{$all_pkgs{$custnum}} ) { +% my $pkgnum = $_->pkgnum; +%# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } ); +% my $part_pkg = $_->part_pkg; +% +% my $pkg = $part_pkg->pkg; +% my $comment = $part_pkg->comment; +% my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum"; +% my @cust_svc = @{shift @lol_cust_svc}; +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my $rowspan = scalar(@cust_svc) || 1; +% +% print $n1, qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!; +% +% my($n2)=''; +% foreach my $cust_svc ( @cust_svc ) { +% my($label, $value, $svcdb) = $cust_svc->label; +% my($svcnum) = $cust_svc->svcnum; +% my($sview) = $p.'view'; +% print $n2,qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; +% $n2="</TR><TR>"; +% } +% +% unless ( @cust_svc ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=2> </TD>!; +% } +% +% #print qq!</TR><TR>\n!; +% $n1="</TR><TR>"; +% } +% +% unless ( @{$all_pkgs{$custnum}} ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=3> </TD>!; +% } +% +% print "</TR>"; +% } +% +% + + + </TABLE><% $pager %> + + <% include('/elements/footer.html') %> +% } +% +%#undef $cache; #does this help? +% +%# +% +%sub last_sort { +% lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first); +%} +% +%sub company_sort { +% return -1 if $a->company && ! $b->company; +% return 1 if ! $a->company && $b->company; +% lc($a->company) cmp lc($b->company) +% || lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first);; +%} +% +%sub custnum_sort { +% $a->getfield('custnum') <=> $b->getfield('custnum'); +%} +% +%sub tickets_sort { +% $b->getfield('tickets') <=> $a->getfield('tickets'); +%} +% +%sub custnumsearch { +% +% my $custnum = $cgi->param('custnum_text'); +% $custnum =~ s/\D//g; +% $custnum =~ /^(\d{1,23})$/ or eidiot "Illegal customer number\n"; +% $custnum = $1; +% +% [ qsearchs('cust_main', { 'custnum' => $custnum } ) ]; +%} +% +%sub cardsearch { +% +% my($card)=$cgi->param('card'); +% $card =~ s/\D//g; +% $card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n"; +% my($payinfo)=$1; +% +% [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}), +% qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'}) +% ]; +%} +% +%sub referralsearch { +% $cgi->param('referral_custnum') =~ /^(\d+)$/ +% or eidiot "Illegal referral_custnum"; +% my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } ) +% or eidiot "Customer $1 not found"; +% my $depth; +% if ( $cgi->param('referral_depth') ) { +% $cgi->param('referral_depth') =~ /^(\d+)$/ +% or eidiot "Illegal referral_depth"; +% $depth = $1; +% } else { +% $depth = 1; +% } +% [ $cust_main->referral_cust_main($depth) ]; +%} +% +%sub lastsearch { +% my(%last_type); +% my @cust_main; +% foreach ( $cgi->param('last_type') ) { +% $last_type{$_}++; +% } +% +% $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/ +% or eidiot "Illegal last name"; +% my($last)=$1; +% +% if ( $last_type{'Exact'} || $last_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => $last } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => $last } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $last_type{'Substring'} || $last_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $last_type{'Fuzzy'} || $last_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'last' => $last } ); +% } +% +% #if ($last_type{'Sound-alike'}) { +% #} +% +% \@cust_main; +%} +% +%sub companysearch { +% +% my(%company_type); +% my @cust_main; +% foreach ( $cgi->param('company_type') ) { +% $company_type{$_}++ +% }; +% +% $cgi->param('company_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot "Illegal company"; +% my $company = $1; +% +% if ( $company_type{'Exact'} || $company_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => $company } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => $company } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $company_type{'Substring'} || $company_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } }) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $company_type{'Fuzzy'} || $company_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'company' => $company } ); +% } +% +% if ($company_type{'Sound-alike'}) { +% } +% +% \@cust_main; +%} +% +%sub address2search { +% my @cust_main; +% +% $cgi->param('address2_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or eidiot "Illegal address2"; +% my $address2 = $1; +% +% push @cust_main, qsearch( 'cust_main', +% { 'address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ); +% push @cust_main, qsearch( 'cust_main', +% { 'address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% \@cust_main; +%} +% +%sub phonesearch { +% my @cust_main; +% +% my $phone = $cgi->param('phone_text'); +% +% #(no longer really) false laziness with Record::ut_phonen +% #only works with US/CA numbers... +% $phone =~ s/\D//g; +% if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) { +% $phone = "$1-$2-$3"; +% $phone .= " x$4" if $4; +% } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) { +% $phone = "$1-$2"; +% } elsif ( $phone =~ /^(\d{3,4})$/ ) { +% $phone = $1; +% } else { +% eidiot gettext('illegal_phone'). ": $phone"; +% } +% +% my @fields = qw(daytime night fax); +% push @fields, qw(ship_daytime ship_night ship_fax) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% for my $field ( @fields ) { +% push @cust_main, qsearch ( 'cust_main', +% { $field => { 'op' => 'LIKE', +% 'value' => "%$phone%" } } ); +% } +% +% \@cust_main; +%} +% +% + diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html new file mode 100755 index 000000000..4f7508447 --- /dev/null +++ b/httemplate/search/cust_main.html @@ -0,0 +1,42 @@ +<HTML> + <HEAD> + <TITLE>Customer Search</TITLE> + </HEAD> + <BODY BGCOLOR="#e8e8e8"> + <FONT SIZE=7> + Customer Search + </FONT> + <BR><BR> + <FORM ACTION="cust_main.cgi" METHOD="GET"> + <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>Fuzzy + <OPTION>Substring + <OPTION>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>Fuzzy + <OPTION>Substring + <OPTION>Exact + </SELECT> + + <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while. Please be patient. + + </FORM> + + <HR>Explanation of search methods: + <UL> + <LI><B>All</B> - Try all search methods. + <LI><B>Fuzzy</B> - Searches for matches that are close to your text. + <LI><B>Substring</B> - Searches for matches that contain your text. + <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods. + </UL> + </BODY> +</HTML> + diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi new file mode 100755 index 000000000..0664bf796 --- /dev/null +++ b/httemplate/search/cust_pay.cgi @@ -0,0 +1,195 @@ +% +% my $title = 'Payment Search Results'; +% my( $count_query, $sql_query ); +% if ( $cgi->param('magic') ) { +% +% my @search = (); +% my $orderby; +% if ( $cgi->param('magic') eq '_date' ) { +% +% +% if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% push @search, "agentnum = $1"; # $search{'agentnum'} = $1; +% my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +% die "unknown agentnum $1" unless $agent; +% $title = $agent->agent. " $title"; +% } +% +% if ( $cgi->param('payby') ) { +% $cgi->param('payby') =~ +% /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ +% or die "illegal payby ". $cgi->param('payby'); +% push @search, "cust_pay.payby = '$1'"; +% if ( $3 ) { +% if ( $3 eq 'VisaMC' ) { +% #avoid posix regexes for portability +% push @search, +% " ( ( substring(cust_pay.payinfo from 1 for 1) = '4' ". +% " AND substring(cust_pay.payinfo from 1 for 4) != '4936' ". +% " AND substring(cust_pay.payinfo from 1 for 6) ". +% " NOT SIMILAR TO '49030[2-9]' ". +% " AND substring(cust_pay.payinfo from 1 for 6) ". +% " NOT SIMILAR TO '49033[5-9]' ". +% " AND substring(cust_pay.payinfo from 1 for 6) ". +% " NOT SIMILAR TO '49110[1-2]' ". +% " AND substring(cust_pay.payinfo from 1 for 6) ". +% " NOT SIMILAR TO '49117[4-9]' ". +% " AND substring(cust_pay.payinfo from 1 for 6) ". +% " NOT SIMILAR TO '49118[1-2]' ". +% " )". +% " OR substring(cust_pay.payinfo from 1 for 2) = '51' ". +% " OR substring(cust_pay.payinfo from 1 for 2) = '52' ". +% " OR substring(cust_pay.payinfo from 1 for 2) = '53' ". +% " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". +% " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". +% " OR substring(cust_pay.payinfo from 1 for 2) = '55' ". +% " ) "; +% } elsif ( $3 eq 'Amex' ) { +% push @search, +% " ( substring(cust_pay.payinfo from 1 for 2 ) = '34' ". +% " OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ". +% " ) "; +% } elsif ( $3 eq 'Discover' ) { +% push @search, +% " ( substring(cust_pay.payinfo from 1 for 4 ) = '6011' ". +% " OR substring(cust_pay.payinfo from 1 for 3 ) = '650' ". +% " ) "; +% } elsif ( $3 eq 'Maestro' ) { +% push @search, +% " ( substring(cust_pay.payinfo from 1 for 2 ) = '63' ". +% " OR substring(cust_pay.payinfo from 1 for 2 ) = '67' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ". +% " OR substring(cust_pay.payinfo from 1 for 4 ) = '4936' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) ". +% " SIMILAR TO '49030[2-9]' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) ". +% " SIMILAR TO '49033[5-9]' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) ". +% " SIMILAR TO '49110[1-2]' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) ". +% " SIMILAR TO '49117[4-9]' ". +% " OR substring(cust_pay.payinfo from 1 for 6 ) ". +% " SIMILAR TO '49118[1-2]' ". +% " ) "; +% } else { +% die "unknown card type $3"; +% } +% } +% } +% +% my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +% push @search, "_date >= $beginning ", +% "_date <= $ending"; +% +% $orderby = '_date'; +% +% } elsif ( $cgi->param('magic') eq 'paybatch' ) { +% +% $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ +% or die "illegal paybatch: ". $cgi->param('paybatch'); +% +% push @search, "paybatch = '$1'"; +% +% $orderby = "LOWER(company || ' ' || last || ' ' || first )"; +% +% } else { +% die "unknown search magic: ". $cgi->param('magic'); +% } +% +% #here is the agent virtualization +% push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% my $search = ' WHERE '. join(' AND ', @search); +% +% $count_query = "SELECT COUNT(*), SUM(paid) ". +% "FROM cust_pay LEFT JOIN cust_main USING ( custnum )". +% $search; +% +% $sql_query = { +% 'table' => 'cust_pay', +% 'select' => join(', ', +% 'cust_pay.*', +% 'cust_main.custnum as cust_main_custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'hashref' => {}, +% 'extra_sql' => "$search ORDER BY $orderby", +% 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', +% }; +% +% } else { +% +% $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; +% my $payinfo = $1; +% +% $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; +% my $payby = $1; +% +% $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay". +% " WHERE payinfo = '$payinfo' AND payby = '$payby'". +% " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% $sql_query = { +% 'table' => 'cust_pay', +% 'hashref' => { 'payinfo' => $payinfo, +% 'payby' => $payby }, +% 'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql. +% " ORDER BY _date", +% }; +% +% } +% +% my $link = sub { +% my $cust_pay = shift; +% $cust_pay->cust_main_custnum +% ? [ "${p}view/cust_main.cgi?", 'custnum' ] +% : ''; +% }; +% +% +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'payments', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total paid', ], + 'header' => [ 'Payment', + 'Amount', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + sub { + my $cust_pay = shift; + if ( $cust_pay->payby eq 'CARD' ) { + 'Card #'. $cust_pay->payinfo_masked; + } elsif ( $cust_pay->payby eq 'CHEK' ) { + 'E-check acct#'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'BILL' ) { + 'Check #'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'PREP' ) { + 'Prepaid card #'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'CASH' ) { + 'Cash '. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'WEST' ) { + 'Western Union'; #. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'MCRD' ) { + 'Manual credit card'; #. $cust_pay->payinfo; + } else { + $cust_pay->payby. ' '. $cust_pay->payinfo; + } + }, + sub { sprintf('$%.2f', shift->paid ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + #'align' => 'lrrrll', + 'align' => 'rrr', + 'links' => [ + '', + '', + '', + ( map { $link } FS::UI::Web::cust_header() ), + ], + ) +%> diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi new file mode 100755 index 000000000..d12e3c44f --- /dev/null +++ b/httemplate/search/cust_pay_batch.cgi @@ -0,0 +1,169 @@ +%my( $count_query, $sql_query ); +%my $hashref = {}; +%my @search = (); +%my $orderby = 'paybatchnum'; +% +%my( $pay_batch, $batchnum ) = ( '', ''); +%if ( $cgi->param('batchnum') && $cgi->param('batchnum') =~ /^(\d+)$/ ) { +% push @search, "batchnum = $1"; +% $pay_batch = qsearchs('pay_batch', { 'batchnum' => $1 } ); +% die "Batch $1 not found!" unless $pay_batch; +% $batchnum = $pay_batch->batchnum; +%} +% +%if ( $cgi->param('payby') ) { +% $cgi->param('payby') =~ /^(CARD|CHEK)$/ +% or die "illegal payby " . $cgi->param('payby'); +% +% push @search, "cust_pay_batch.payby = '$1'"; +%} +% +%if ( not $cgi->param('dcln') ) { +% push @search, "cpb.status IS DISTINCT FROM 'Approved'"; +%} +% +%my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +%unless ($pay_batch){ +% push @search, "pay_batch.upload >= $beginning" if ($beginning); +% push @search, "pay_batch.upload <= $ending" if ($ending < 4294967295);#2^32-1 +% $orderby = "pay_batch.download,paybatchnum"; +%} +% +%push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; +%my $search = ' WHERE ' . join(' AND ', @search); +% +%$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' . +% 'LEFT JOIN cust_main USING ( custnum ) ' . +% 'LEFT JOIN pay_batch USING ( batchnum )' . +% $search; +% +%#grr +%$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," . +% "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " . +% "FROM cust_pay_batch AS cpb " . +% 'LEFT JOIN cust_main USING ( custnum ) ' . +% 'LEFT JOIN pay_batch USING ( batchnum ) ' . +% "$search ORDER BY $orderby"; +% +%my $html_init = ''; +%if ( $pay_batch ) { +% my $conf = new FS::Conf; +% my $fixed = $conf->config('batch-fixed_format-'. $pay_batch->payby); +% if ( +% $pay_batch->status eq 'O' +% || ( $pay_batch->status eq 'I' +% && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') +% ) +% ) { +% $html_init .= qq!<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">!; +% if ( $fixed ) { +% $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; +% } else { +% $html_init .= qq!Download batch in format <SELECT NAME="format">!. +% qq!<OPTION VALUE="">Default batch mode</OPTION>!. +% qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>!. +% qq!<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>!. +% qq!<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>!. +% qq!</SELECT>!; +% } +% $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR>!; +% } +% +% if ( +% $pay_batch->status eq 'I' +% || ( $pay_batch->status eq 'R' +% && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') +% ) +% ) { +% $html_init .= qq!<FORM ACTION="$p/misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">!. +% qq!Upload results<BR>!. +% qq!Filename <INPUT TYPE="file" NAME="batch_results"><BR>!; +% if ( $fixed ) { +% $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; +% } else { +% $html_init .= qq!Format <SELECT NAME="format">!. +% qq!<OPTION VALUE="">Default batch mode</OPTION>!. +% qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>!. +% qq!<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>!. +% qq!<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>!. +% qq!</SELECT><BR>!; +% } +% $html_init .= '<INPUT TYPE="submit" VALUE="Upload"></FORM><BR>'; +% } +% +%} +% +%if ($pay_batch) { +% my $sth = dbh->prepare($count_query) or die dbh->errstr. "doing $count_query"; +% $sth->execute or die "Error executing \"$count_query\": ". $sth->errstr; +% my $cards = $sth->fetchrow_arrayref->[0]; +% +% my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". $batchnum; +% $sth = dbh->prepare($st) or die dbh->errstr. "doing $st"; +% $sth->execute or die "Error executing \"$st\": ". $sth->errstr; +% my $total = $sth->fetchrow_arrayref->[0]; +% +% $html_init .= "$cards credit card payments batched<BR>\$" . +% sprintf("%.2f", $total) ." total in batch<BR>"; +%} +% +% +<% include('elements/search.html', + 'title' => 'Batch payment details', + 'name' => 'batch details', + 'menubar' => ['Main Menu' => $p,], + 'query' => $sql_query, + 'count_query' => $count_query, + 'html_init' => $pay_batch ? $html_init : '', + 'header' => [ '#', + 'Inv #', + 'Customer', + 'Customer', + 'Card Name', + 'Card', + 'Exp', + 'Amount', + 'Status', + ], + 'fields' => [ sub { + shift->[0]; + }, + sub { + shift->[1]; + }, + sub { + shift->[2]; + }, + sub { + my $cpb = shift; + $cpb->[3] . ', ' . $cpb->[4]; + }, + sub { + shift->[5]; + }, + sub { + my $cardnum = shift->[6]; + 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); + }, + sub { + shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my( $mon, $year ) = ( $2, $1 ); + $mon = "0$mon" if length($mon) == 1; + "$mon/$year"; + }, + sub { + shift->[8]; + }, + sub { + shift->[9]; + }, + ], + 'align' => 'lllllllrl', + 'links' => [ ['', sub{'#';}], + ["${p}view/cust_bill.cgi?", sub{shift->[1];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ], + ) +%> + diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi new file mode 100755 index 000000000..8dbc600e9 --- /dev/null +++ b/httemplate/search/cust_pkg.cgi @@ -0,0 +1,320 @@ +% +% +%# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); +% +%my($query) = $cgi->keywords; +% +%my @where = (); +% +%## +%# parse agent +%## +% +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { +% push @where, +% "agentnum = $1"; +%} +% +%## +%# parse status +%## +% +%if ( $cgi->param('magic') eq 'active' +% || $cgi->param('status') eq 'active' ) { +% +% push @where, FS::cust_pkg->active_sql(); +% +%} elsif ( $cgi->param('magic') eq 'inactive' +% || $cgi->param('status') eq 'inactive' ) { +% +% push @where, FS::cust_pkg->inactive_sql(); +% +% +%} elsif ( $cgi->param('magic') eq 'suspended' +% || $cgi->param('status') eq 'suspended' ) { +% +% push @where, FS::cust_pkg->suspended_sql(); +% +%} elsif ( $cgi->param('magic') =~ /^cancell?ed$/ +% || $cgi->param('status') =~ /^cancell?ed$/ ) { +% +% push @where, FS::cust_pkg->cancelled_sql(); +% +%} elsif ( $cgi->param('status') =~ /^(one-time charge|inactive)$/ ) { +% +% push @where, FS::cust_pkg->inactive_sql(); +% +%} +% +%### +%# parse package class +%### +% +%#false lazinessish w/graph/cust_bill_pkg.cgi +%my $classnum = 0; +%my @pkg_class = (); +%if ( exists($cgi->Vars->{'classnum'}) +% && $cgi->param('classnum') =~ /^(\d*)$/ +% ) +%{ +% $classnum = $1; +% if ( $classnum ) { #a specific class +% push @where, "classnum = $classnum"; +% +% #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); +% #die "classnum $classnum not found!" unless $pkg_class[0]; +% #$title .= $pkg_class[0]->classname.' '; +% +% } elsif ( $classnum eq '' ) { #the empty class +% +% push @where, "classnum IS NULL"; +% #$title .= 'Empty class '; +% #@pkg_class = ( '(empty class)' ); +% } elsif ( $classnum eq '0' ) { +% #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); +% #push @pkg_class, '(empty class)'; +% } else { +% die "illegal classnum"; +% } +%} +%#eslaf +% +%### +%# parse magic, legacy, etc. +%### +% +%my $orderby; +%if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { +% $orderby = 'ORDER BY bill'; +% +% my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +% push @where, +% #"bill >= $beginning ", +% #"bill <= $ending", +% "CASE WHEN bill IS NULL THEN 0 ELSE bill END >= $beginning ", +% "CASE WHEN bill IS NULL THEN 0 ELSE bill END <= $ending", +% #'( cancel IS NULL OR cancel = 0 )' +% ; +% +%} else { +% +% if ( $cgi->param('magic') && +% $cgi->param('magic') =~ /^(active|inactive|suspended|cancell?ed)$/ +% ) { +% +% $orderby = 'ORDER BY pkgnum'; +% +% if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) { +% push @where, "pkgpart = $1"; +% } +% +% } elsif ( $query eq 'pkgnum' ) { +% +% $orderby = 'ORDER BY pkgnum'; +% +% } elsif ( $query eq 'APKG_pkgnum' ) { +% +% $orderby = 'ORDER BY pkgnum'; +% +% push @where, '0 < ( +% SELECT count(*) FROM pkg_svc +% WHERE pkg_svc.pkgpart = cust_pkg.pkgpart +% AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc +% WHERE cust_svc.pkgnum = cust_pkg.pkgnum +% AND cust_svc.svcpart = pkg_svc.svcpart +% ) +% )'; +% +% } else { +% die "Empty or unknown QUERY_STRING!"; +% } +% +%} +% +%## +%# setup queries, links, subs, etc. for the search +%## +% +%# here is the agent virtualization +%push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; +% +%my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '. +% 'LEFT JOIN part_pkg USING ( pkgpart ) '. +% 'LEFT JOIN pkg_class USING ( classnum ) '; +% +%my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql"; +% +%my $sql_query = { +% 'table' => 'cust_pkg', +% 'hashref' => {}, +% 'select' => join(', ', +% 'cust_pkg.*', +% ( map "part_pkg.$_", qw( pkg freq ) ), +% 'pkg_class.classname', +% 'cust_main.custnum as cust_main_custnum', +% FS::UI::Web::cust_sql_fields( +% $cgi->param('cust_fields') +% ), +% ), +% 'extra_sql' => "$extra_sql $orderby", +% 'addl_from' => $addl_from, +%}; +% +%my $link = sub { +% [ "${p}view/cust_main.cgi?".shift->custnum.'#cust_pkg', 'pkgnum' ]; +%}; +% +%my $clink = sub { +% my $cust_pkg = shift; +% $cust_pkg->cust_main_custnum +% ? [ "${p}view/cust_main.cgi?", 'custnum' ] +% : ''; +%}; +% +%#if ( scalar(@cust_pkg) == 1 ) { +%# print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum. +%# "#cust_pkg". $cust_pkg[0]->pkgnum ); +% +%# my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); +%# my $rowspan = scalar(@cust_svc) || 1; +% +%# my $n2 = ''; +%# foreach my $cust_svc ( @cust_svc ) { +%# my($label, $value, $svcdb) = $cust_svc->label; +%# my $svcnum = $cust_svc->svcnum; +%# my $sview = $p. "view"; +%# print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, +%# qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; +%# $n2="</TR><TR>"; +%# } +% +%sub time_or_blank { +% my $column = shift; +% return sub { +% my $record = shift; +% my $value = $record->get($column); #mmm closures +% $value ? time2str('%b %d %Y', $value ) : ''; +% }; +%} +% +%### +%# and finally, include the search template +%### +% +% +<% include( 'elements/search.html', + 'title' => 'Package Search Results', + 'name' => 'packages', + 'query' => $sql_query, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ '#', + 'Package', + 'Class', + 'Status', + 'Freq.', + 'Setup', + 'Last bill', + 'Next bill', + 'Susp.', + 'Expire', + 'Cancel', + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), + 'Services', + ], + 'fields' => [ + 'pkgnum', + sub { #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->pkg; # ' - '. $part_pkg->comment; + $_[0]->pkg; # ' - '. $_[0]->comment; + }, + 'classname', + sub { ucfirst(shift->status); }, + sub { #shift->part_pkg->freq_pretty; + + #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->freq_pretty; + + FS::part_pkg::freq_pretty(shift); + }, + + #sub { time2str('%b %d %Y', shift->setup); }, + #sub { time2str('%b %d %Y', shift->last_bill); }, + #sub { time2str('%b %d %Y', shift->bill); }, + #sub { time2str('%b %d %Y', shift->susp); }, + #sub { time2str('%b %d %Y', shift->expire); }, + #sub { time2str('%b %d %Y', shift->get('cancel')); }, + ( map { time_or_blank($_) } + qw( setup last_bill bill susp expire cancel ) ), + + \&FS::UI::Web::cust_fields, + #sub { '<table border=0 cellspacing=0 cellpadding=0 STYLE="border:none">'. + # join('', map { '<tr><td align="right" style="border:none">'. $_->[0]. + # ':</td><td style="border:none">'. $_->[1]. '</td></tr>' } + # shift->labels + # ). + # '</table>'; + # }, + sub { + [ map { + [ + { 'data' => $_->[0]. ':', + 'align'=> 'right', + }, + { 'data' => $_->[1], + 'align'=> 'left', + 'link' => $p. 'view/' . + $_->[2]. '.cgi?'. $_->[3], + }, + ]; + } shift->labels + ]; + }, + ], + 'color' => [ + '', + '', + '', + sub { shift->statuscolor; }, + '', + '', + '', + '', + '', + '', + '', + ( map { '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + '', + ], + 'style' => [ '', '', '', 'b' ], + 'size' => [ '', '', '', '-1', ], + 'align' => 'rllclrrrrrr', + 'links' => [ + $link, + $link, + '', + '', + '', + '', + '', + '', + '', + '', + '', + ( map { $clink } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + '', + ], + ) +%> diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi new file mode 100644 index 000000000..990e344b7 --- /dev/null +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -0,0 +1,159 @@ +<% include( 'elements/search.html', + 'title' => 'Tax exemptions', + 'name' => 'tax exemptions', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Month', + 'Amount', + 'Line item', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'exemptpkgnum', + sub { $_[0]->month. '/'. $_[0]->year; }, + sub { $money_char. $_[0]->amount; }, + + sub { + $_[0]->billpkgnum. ': '. + ( $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + ). + ' ('. + ( $_[0]->setup > 0 + ? $money_char. $_[0]->setup. ' setup' + : '' + ). + ( $_[0]->setup > 0 && $_[0]->recur > 0 + ? ' / ' + : '' + ). + ( $_[0]->recur > 0 + ? $money_char. $_[0]->recur. ' recur' + : '' + ). + ')'; + }, + + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + + '', + $ilink, + $ilink, + + ( map { $clink } FS::UI::Web::cust_header() ), + ], + 'align' => 'rrrlrc', # 'rlrrrc', + ) +%> +<%once> + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; + +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $join = " + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust + $join_pkg +"; + +</%once> +<%init> + +my @where = (); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +if ( $beginning || $ending ) { + push @where, "_date >= $beginning", + "_date <= $ending"; + #"payby != 'COMP'; +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "agentnum = $1"; +} + +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.custnum = $1"; +} + +if ( $cgi->param('out') ) { + + push @where, " + 0 = ( + SELECT COUNT(*) FROM cust_main_county AS county_out + WHERE ( county_out.county = cust_main.county + OR ( county_out.county IS NULL AND cust_main.county = '' ) + OR ( county_out.county = '' AND cust_main.county IS NULL) + OR ( county_out.county IS NULL AND cust_main.county IS NULL) + ) + AND ( county_out.state = cust_main.state + OR ( county_out.state IS NULL AND cust_main.state = '' ) + OR ( county_out.state = '' AND cust_main.state IS NULL ) + OR ( county_out.state IS NULL AND cust_main.state IS NULL ) + ) + AND county_out.country = cust_main.country + AND county_out.tax > 0 + ) + "; + +} elsif ( $cgi->param('country' ) ) { + + my $county = dbh->quote( $cgi->param('county') ); + my $state = dbh->quote( $cgi->param('state') ); + my $country = dbh->quote( $cgi->param('country') ); + push @where, "( county = $county OR $county = '' )", + "( state = $state OR $state = '' )", + " country = $country"; + push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') ) + if $cgi->param('taxclass'); + +} + +my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; + +my $count_query = "SELECT COUNT(*), SUM(amount)". + " FROM cust_tax_exempt_pkg $join $where"; + +my $query = { + 'table' => 'cust_tax_exempt_pkg', + 'addl_from' => $join, + 'hashref' => {}, + 'select' => join(', ', + 'cust_tax_exempt_pkg.*', + 'cust_bill_pkg.*', + 'cust_bill.*', + 'part_pkg.pkg', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $where, +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +</%init> diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html new file mode 100644 index 000000000..14e1dd095 --- /dev/null +++ b/httemplate/search/elements/search.html @@ -0,0 +1,548 @@ +% +% +% # options example... +% # (everything not commented required is optional) +% # +% # # basic options, required +% # 'title' => 'Page title', +% # +% # 'name_singular' => 'item', #singular name for the records returned +% # #OR# # (preferred, will be pluralized automatically) +% # 'name' => 'items', #plural name for the records returned +% # # (deprecated, will be singularlized +% # # simplisticly) +% # +% # # some HTML callbacks... +% # 'menubar' => '', #menubar arrayref +% # 'html_init' => '', #after the header/menubar and before the pager +% # 'html_foot' => '', #at the bottom +% # 'html_posttotal' => '', #at the bottom +% # # (these three can be strings or coderefs) +% # +% # +% # #literal SQL query string or qsearch hashref, required +% # 'query' => { +% # 'table' => 'tablename', +% # #everything else is optional... +% # 'hashref' => { 'field' => 'value', +% # 'field' => { 'op' => '<', +% # 'value' => '54', +% # }, +% # }, +% # 'select' => '*', +% # 'addl_from' => '', #'LEFT JOIN othertable USING ( key )', +% # 'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff', +% # +% # +% # }, +% # # "select * from tablename"; +% # +% # #required unless 'query' is an SQL query string (shouldn't be...) +% # 'count_query' => 'SELECT COUNT(*) FROM tablename', +% # +% # 'count_addl' => [], #additional count fields listref of sprintf strings +% # # [ $money_char.'%.2f total paid', ], +% # +% # #listref of column labels, <TH> +% # #required unless 'query' is an SQL query string +% # # (if not specified the database column names will be used) +% # 'header' => [ '#', 'Item' ], +% # +% # 'disable_download' => '', # set true to hide the CSV/Excel download links +% # 'disable_nonefound' => '', # set true to disable the "No matching Xs found" +% # # message +% # +% # #listref - each item is a literal column name (or method) or coderef +% # #if not specified all columns will be shown +% # 'fields' => [ +% # 'column', +% # sub { my $row = shift; $row->column; }, +% # ], +% # +% # #listref of column footers +% # 'footer' => [], +% # +% # #listref - each item is the empty string, or a listref of ... +% # 'links' => +% # +% # +% # 'align' => 'lrc.', #one letter for each column, left/right/center/none +% # # can also pass a listref with full values: +% # # [ 'left', 'right', 'center', '' ] +% # +% # #listrefs... +% # #currently only HTML, maybe eventually Excel too +% # 'color' => [], +% # 'size' => [], +% # 'style' => [], +% # +% # #redirect if there's only one item... +% # # listref of URL base and column name (or method) +% # # or a coderef that returns the same +% # 'redirect' => +% +% my $DEBUG = 0; +% +% my(%opt) = @_; +% #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n"; +% +% my %align = ( +% 'l' => 'left', +% 'r' => 'right', +% 'c' => 'center', +% ' ' => '', +% '.' => '', +% ); +% $opt{align} = [ map $align{$_}, split(//, $opt{align}) ], +% unless !$opt{align} || ref($opt{align}); +% +% my $type = ''; +% my $limit = ''; +% my($maxrecords, $total, $offset, $count_arrayref); +% +% if ( $cgi->param('_type') =~ /^(csv|\w*\.xls)$/ ) { +% +% $type = $1; +% +% } else { #setup some pagination things if we're in html mode +% +% 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; +% $maxrecords = $conf->config('maxsearchrecordsperpage'); +% +% $limit = $maxrecords ? "LIMIT $maxrecords" : ''; +% +% $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; +% $count_arrayref = $count_sth->fetchrow_arrayref; +% $total = $count_arrayref->[0]; +% +% } +% +% # run the query +% +% my $header = $opt{'header'}; +% my $rows; +% if ( ref($opt{'query'}) ) { +% +% #eval "use FS::$opt{'query'};"; +% $rows = [ qsearch( +% $opt{'query'}->{'table'}, +% $opt{'query'}->{'hashref'} || {}, +% $opt{'query'}->{'select'}, +% $opt{'query'}->{'extra_sql'}. " $limit", +% '', +% (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : '') +% ) ]; +% +% } else { +% +% 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? +% $rows = $sth->fetchall_arrayref; +% +% $header ||= $sth->{NAME}; +% +% } +% +% warn scalar(@$rows). ' rows returned from '. +% ( ref($opt{'query'}) ? 'qsearch query' : 'literal SQL query' ) +% if $DEBUG || $opt{'debug'}; +% +% # display the results - csv, xls or html +% +% if ( $type eq 'csv' ) { +% +% #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes +% http_header('Content-Type' => 'text/plain' ); +% +% my $csv = new Text::CSV_XS { 'always_quote' => 1, +% 'eol' => "\n", #"\015\012", #"\012" +% }; +% +% $csv->combine(@$header); #or die $csv->status; +% +<% $csv->string %> +% +% +% foreach my $row ( @$rows ) { +% +% if ( $opt{'fields'} ) { +% +% my @line = (); +% +% foreach my $field ( @{$opt{'fields'}} ) { +% if ( ref($field) eq 'CODE' ) { +% push @line, map { +% ref($_) eq 'ARRAY' +% ? '(N/A)' #unimplemented +% : $_; +% } +% &{$field}($row); +% } else { +% push @line, $row->$field(); +% } +% } +% +% $csv->combine(@line); #or die $csv->status; +% +% } else { +% $csv->combine(@$row); #or die $csv->status; +% } +% +% +<% $csv->string %> +% +% +% } +% +% #} elsif ( $type eq 'excel' ) { +% } elsif ( $type =~ /\.xls$/ ) { +% +% #http_header('Content-Type' => 'application/excel' ); #eww +% http_header('Content-Type' => 'application/vnd.ms-excel' ); +% #http_header('Content-Type' => 'application/msexcel' ); #alas +% +% my $data = ''; +% my $XLS = new IO::Scalar \$data; +% my $workbook = Spreadsheet::WriteExcel->new($XLS) +% or die "Error opening .xls file: $!"; +% +% my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); +% +% my($r,$c) = (0,0); +% +% $worksheet->write($r, $c++, $_) foreach @$header; +% +% foreach my $row ( @$rows ) { +% $r++; +% $c = 0; +% +% if ( $opt{'fields'} ) { +% +% #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% +% foreach my $field ( @{$opt{'fields'}} ) { +% #my $align = $aligns ? shift @$aligns : ''; +% #$align = " ALIGN=$align" if $align; +% #my $a = ''; +% #if ( $links ) { +% # my $link = shift @$links; +% # $link = &{$link}($row) if ref($link) eq 'CODE'; +% # if ( $link ) { +% # my( $url, $method ) = @{$link}; +% # if ( ref($method) eq 'CODE' ) { +% # $a = $url. &{$method}($row); +% # } else { +% # $a = $url. $row->$method(); +% # } +% # $a = qq(<A HREF="$a">); +% # } +% #} +% if ( ref($field) eq 'CODE' ) { +% foreach my $value ( &{$field}($row) ) { +% if ( ref($value) eq 'ARRAY' ) { +% $worksheet->write($r, $c++, '(N/A)' ); #unimplemented +% } else { +% $worksheet->write($r, $c++, $value ); +% } +% } +% } else { +% $worksheet->write($r, $c++, $row->$field() ); +% } +% } +% +% } else { +% $worksheet->write($r, $c++, $_) foreach @$row; +% } +% +% } +% +% $workbook->close();# or die "Error creating .xls file: $!"; +% +% http_header('Content-Length' => length($data) ); +% +<% $data %> +% +% +% } else { # regular HTML +% +% if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 ) { +% my $redirect = $opt{'redirect'}; +% $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE'; +% my( $url, $method ) = @$redirect; +% redirect( $url. $rows->[0]->$method() ); +% } else { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = PL($opt{'name_singular'}); +% } +% ( my $xlsname = $opt{'name'} ) =~ s/\W//g; +% if ( $total == 1 ) { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = $opt{'name_singular'} +% } else { +% #$opt{'name'} =~ s/s$// if $total == 1; +% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1; +% } +% } +% +% my @menubar = (); +% if ( $opt{'menubar'} ) { +% @menubar = @{ $opt{'menubar'} }; +% } else { +% @menubar = ( 'Main menu' => $p ); +% } +% +% +% + + <% include( '/elements/header.html', $opt{'title'}, + include( '/elements/menubar.html', @menubar ) + ) + %> + <% defined($opt{'html_init'}) + ? ( ref($opt{'html_init'}) + ? &{$opt{'html_init'}}() + : $opt{'html_init'} + ) + : '' + %> +% my $pager = include ( '/elements/pager.html', +% 'offset' => $offset, +% 'num_rows' => scalar(@$rows), +% 'total' => $total, +% 'maxrecords' => $maxrecords, +% ); +% +% unless ( $total ) { +% unless ( $opt{'disable_nonefound'} ) { + + No matching <% $opt{'name'} %> found.<BR> +% } +% } else { + + + <TABLE> + <TR> + <TD VALIGN="bottom"> + <% $total %> total <% $opt{'name'} %> + <% defined($opt{'html_posttotal'}) + ? ( ref($opt{'html_posttotal'}) + ? &{$opt{'html_posttotal'}}() + : $opt{'html_posttotal'} + ) + : '' + %> + <BR> +% if ( $opt{'count_addl'} ) { +% my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { + + <% sprintf( $count, $count_arrayref->[++$n] ) %><BR> +% } +% } + + </TD> +% unless ( $opt{'disable_download'} ) { + + <TD ALIGN="right"> +% $cgi->param('_type', "$xlsname.xls" ); + + Download full results<BR> + as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR> +% $cgi->param('_type', 'csv'); + + as <A HREF="<% $cgi->self_url %>">CSV file</A> + </TD> +% } + + </TR> + <TR> + <TD COLSPAN=2> + <% $pager %> + + <% include('/elements/table-grid.html') %> + + <TR> +% +% foreach my $header ( @$header ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"><% $header %></TH> +% } + + </TR> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% foreach my $row ( @$rows ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + <TR> +% if ( $opt{'fields'} ) { +% +% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : []; +% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : []; +% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : []; +% +% foreach my $field ( +% +% map { +% if ( ref($_) eq 'ARRAY' ) { +% +% my $tableref = $_; +% +% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0>'. +% +% join('', map { +% +% my $rowref = $_; +% +% '<tr>'. +% +% join('', map { +% +% my $element = $_; +% +% '<TD'. +% ( $element->{'align'} +% ? ' ALIGN="'. $element->{'align'}. '"' +% : '' +% ). '>'. +% ( $element->{'link'} +% ? '<A HREF="'. $element->{'link'}.'">' +% : '' +% ). +% $element->{'data'}. +% ( $element->{'link'} +% ? '</A>' +% : '' +% ). +% '</td>'; +% +% } @$rowref ). +% +% '</tr>'; +% } @$tableref ). +% +% '</table>'; +% +% } else { +% $_; +% } +% } +% +% map { +% if ( ref($_) eq 'CODE' ) { +% &{$_}($row); +% } else { +% $row->$_(); +% } +% } +% @{$opt{'fields'}} +% +% ) { +% +% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid'; +% +% my $align = $aligns ? shift @$aligns : ''; +% $align = " ALIGN=$align" if $align; +% +% my $a = ''; +% if ( $links ) { +% my $link = shift @$links; +% $link = &{$link}($row) if ref($link) eq 'CODE'; +% if ( $link ) { +% my( $url, $method ) = @{$link}; +% if ( ref($method) eq 'CODE' ) { +% $a = $url. &{$method}($row); +% } else { +% $a = $url. $row->$method(); +% } +% $a = qq(<A HREF="$a">); +% } +% } +% +% my $font = ''; +% my $color = shift @$colors; +% $color = &{$color}($row) if ref($color) eq 'CODE'; +% my $size = shift @$sizes; +% $size = &{$size}($row) if ref($size) eq 'CODE'; +% if ( $color || $size ) { +% $font = '<FONT '. +% ( $color ? "COLOR=#$color " : '' ). +% ( $size ? qq(SIZE="$size" ) : '' ). +% '>'; +% } +% +% my($s, $es) = ( '', '' ); +% my $style = shift @$styles; +% $style = &{$style}($row) if ref($style) eq 'CODE'; +% if ( $style ) { +% $s = join( '', map "<$_>", split('', $style) ); +% $es = join( '', map "</$_>", split('', $style) ); +% } +% +% + + <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>"<% $align %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD> +% } +% } else { +% foreach ( @$row ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD> +% } +% } + + </TR> +% } +% if ( $opt{'footer'} ) { + + <TR> +% foreach my $footer ( @{ $opt{'footer'} } ) { + + <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TH> +% } + + </TR> +% } + + + </TABLE> + <% $pager %> + + </TD> + </TR> + </TABLE> +% } + + <% defined($opt{'html_foot'}) + ? ( ref($opt{'html_foot'}) + ? &{$opt{'html_foot'}}() + : $opt{'html_foot'} + ) + : '' + %> + <% include( '/elements/footer.html' ) %> +% } +% } + diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html new file mode 100644 index 000000000..fc690b33b --- /dev/null +++ b/httemplate/search/inventory_item.html @@ -0,0 +1,109 @@ +% +% +%my $classnum = $cgi->param('classnum'); +%$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; +%$classnum = $1; +% +%my $inventory_class = qsearchs( { +% 'table' => 'inventory_class', +% 'hashref' => { 'classnum' => $classnum }, +%} ); +% +%my $title = $inventory_class->classname. ' Inventory'; +% +%#little false laziness with SQL fragments in inventory_class.pm +%my $extra_sql = ''; +%if ( $cgi->param('avail') ) { +% $extra_sql = 'AND ( svcnum IS NULL OR svcnum = 0 )'; +% $title .= ' - Available'; +%} elsif ( $cgi->param('used') ) { +% $extra_sql = 'AND svcnum IS NOT NULL AND svcnum > 0'; +% $title .= ' - In use'; +%} +% +%my $count_query = +% "SELECT COUNT(*) FROM inventory_item WHERE classnum = $classnum $extra_sql"; +% +%my $link = sub { +% my $inventory_item = shift; +% if ( $inventory_item->svcnum ) { +% [ "${p}view/svc_acct.cgi?", 'svcnum' ]; +% } else { +% ''; +% } +%}; +%my $link_cust = sub { +% my $inventory_item = shift; +% if ( $inventory_item->custnum ) { +% [ "${p}view/cust_main.cgi?", 'custnum' ]; +% } else { +% ''; +% } +%}; +% +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +% +<% include( 'elements/search.html', + + 'title' => $title, + + #less lame to use Lingua:: something to pluralize + 'name' => $inventory_class->classname. 's', + + 'query' => { + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum }, + 'select' => join(', ', + 'inventory_item.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $extra_sql, + 'addl_from' => $addl_from, + }, + + 'count_query' => $count_query, + + 'header' => [ + '#', + $inventory_class->classname, + 'Service', + FS::UI::Web::cust_header(), + ], + + 'fields' => [ + 'itemnum', + 'item', + #'svcnum', #XXX proper full service customer link ala svc_acct + # "unallocated" ? "available" ? + sub { + #this could be way more efficient with a mixin + # like cust_main_Mixin that let us all all the methods + # on data we already have... + my $inventory_item = shift; + my $cust_svc = $inventory_item->cust_svc; + if ( $cust_svc ) { + my($label, $value) = $cust_svc->label; + "$label: $value"; + } else { + '(available)'; + } + }, + + \&FS::UI::Web::cust_fields, + + ], + + 'links' => [ + '', + '', + $link, + ( map { $link_cust } FS::UI::Web::cust_header() ), + ], + + ) +%> diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi new file mode 100755 index 000000000..7b2b9f00b --- /dev/null +++ b/httemplate/search/pay_batch.cgi @@ -0,0 +1,127 @@ +% +% +%my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved'); +%my $hashref = {}; +%my $count_query = 'SELECT COUNT(*) FROM pay_batch'; +% +%my($begin, $end) = ( '', '' ); +% +%my @where; +%if ( $cgi->param('beginning') +% && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { +% $begin = str2time($1); +% push @where, "download >= $begin"; +%} +%if ( $cgi->param('ending') +% && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { +% $end = str2time($1) + 86399; +% push @where, "download < $end"; +%} +% +%my @status; +%if ( $cgi->param('open') ) { +% push @status, "O"; +%} +% +%if ( $cgi->param('intransit') ) { +% push @status, "I"; +%} +% +%if ( $cgi->param('resolved') ) { +% push @status, "R"; +%} +% +%push @where, +% scalar(@status) ? q!(status='! . join(q!' OR status='!, @status) . q!')! +% : q!status='X'!; # kludgy, X is unused at present +% +%my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; +% +%my $link = [ "${p}search/cust_pay_batch.cgi?batchnum=", 'batchnum' ]; +% +<% include( 'elements/search.html', + 'title' => 'Payment Batches', + 'name_singular' => 'batch', + 'query' => { 'table' => 'pay_batch', + 'hashref' => $hashref, + 'extra_sql' => "$extra_sql ORDER BY batchnum DESC", + }, + 'count_query' => "$count_query $extra_sql", + 'header' => [ 'Batch', + 'Type', + 'First Download', + 'Last Upload', + 'Item Count', + 'Amount', + 'Status', + ], + 'align' => 'rcllrrc', + 'fields' => [ 'batchnum', + sub { + FS::payby->shortname(shift->payby); + }, + sub { + my $self = shift; + my $_date = $self->download; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'O' ) { + 'Download batch'; + } else { + ''; + } + }, + sub { + my $self = shift; + my $_date = $self->upload; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'I' ) { + 'Upload results'; + } else { + ''; + } + }, + sub { + my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + $statusmap{shift->status}; + }, + ], + 'links' => [ + $link, + '', + sub { shift->status eq 'O' ? $link : '' }, + sub { shift->status eq 'I' ? $link : '' }, + ], + 'size' => [ + '', + '', + sub { shift->status eq 'O' ? "+1" : '' }, + sub { shift->status eq 'I' ? "+1" : '' }, + ], + 'style' => [ + '', + '', + sub { shift->status eq 'O' ? "b" : '' }, + sub { shift->status eq 'I' ? "b" : '' }, + ], + ) + +%> + + diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html new file mode 100644 index 000000000..a966f68f5 --- /dev/null +++ b/httemplate/search/pay_batch.html @@ -0,0 +1,27 @@ +<% include('/elements/header.html', 'Batch criteria' ) %> + +<FORM ACTION="pay_batch.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show open batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="intransit" VALUE="1" CHECKED></TD> + <TD>Show in-transit batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="resolved" VALUE="1" CHECKED></TD> + <TD>Show resolved batches</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Batches"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html new file mode 100644 index 000000000..dff8a3d9a --- /dev/null +++ b/httemplate/search/prepay_credit.html @@ -0,0 +1,44 @@ +% +%my $agent = ''; +%my $hashref = {}; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $hashref->{agentnum} = $1; +% $agent = qsearchs('agent', { 'agentnum' => $1 } ); +%} +% +%my $count_query = 'SELECT COUNT(*) FROM prepay_credit'; +%$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; +% +% +<% include( 'elements/search.html', + 'title' => 'Unused Prepaid Cards'. + ($agent ? ' for '. $agent->agent : ''), + 'menubar' => [ + 'Main menu' => $p, + 'Generate cards' => $p.'edit/prepay_credit.cgi', + ], + 'name' => 'prepaid cards', + 'query' => { 'table' => 'prepay_credit', + 'hashref' => $hashref, + }, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ '#', qw(Amount Time Agent) ], + 'fields' => [ + 'identifier', + sub { sprintf('$%.2f', shift->amount ) }, + sub { my $c = shift; $c ? duration_exact($c->seconds) : '' }, + sub { my $agent = shift->agent; + $agent ? $agent->agent : ''; + }, + ], + 'links' => [ + '', + '', + '', + sub { my $agent = shift->agent; + $agent ? [ "${p}view/agent.cgi?", 'agentnum' ] : ''; + }, + ], + ) +%> diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html new file mode 100644 index 000000000..fa6c1a1ca --- /dev/null +++ b/httemplate/search/queue.html @@ -0,0 +1,125 @@ +% +% +%my $hashref = {}; +% +%my $conf = new FS::Conf; +%my $dangerous = $conf->exists('queue_dangerous_controls'); +% +%my $noactions = 0; +% +%my $count_query = 'SELECT COUNT(*) FROM queue'; # + $hashref +% +%my $areboxes = 0; +% +% +<% include( 'elements/search.html', + 'title' => 'Job Queue', + 'menubar' => [ 'Main menu' => $p, ], + 'name' => 'jobs', + 'query' => { 'table' => 'queue', + 'hashref' => $hashref, + 'extra_sql' => 'ORDER BY jobnum', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Job', + 'Args', + 'Date', + 'Status', + 'Account', # unless $hashref->{'svcnum'} + '', # checkbox column + ], + 'fields' => [ + 'jobnum', + 'job', + sub { + my $queue = shift; + if ( $dangerous + || $queue->job !~ /^FS::part_export::/ + || !$noactions + ) + { + encode_entities( join(' ', $queue->args) ); + } else { + ''; + } + }, + sub { + time2str( "%a %b %e %T %Y", shift->_date ); + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + $status .= ': '. $queue->statustext + if $queue->statustext; + my @queue_depend = $queue->queue_depend; + $status .= ' (waiting for '. + join(', ', map { $_->depend_jobnum } + @queue_depend + ). + ')' + if @queue_depend; + my $changable = $dangerous + || ( ! $noactions + && $status =~ /^failed/ + || $status =~ /^locked/ + ); + if ( $changable ) { + $status .= + qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!. + qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!; + } + $status; + }, + sub { + my $queue = shift; + # return '' if $hashref->{'svcnum'} + my $cust_svc = $queue->cust_svc; + my $account; + if ( $cust_svc ) { + my $table = $cust_svc->part_svc->svcdb; + my $label = ( $cust_svc->label )[1]; + qq!<A HREF="../view/$table.cgi?!. $queue->svcnum. + qq!">$label</A>!; + } else { + ''; + } + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + my $changable = $dangerous + || ( ! $noactions + && $status eq 'failed' + || $status eq 'locked' + ); + if ( $changable ) { + $areboxes = 1; + qq!<INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1">!; + } else { + ''; + } + }, + ], + #'links' => [ + # '', + # '', + # '', + # '', + # '', + # '', #$acct_link, + # '', + # ], + 'html_foot' => sub { + if ( $areboxes ) { + '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'. + '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>'; + } else { + ''; + } + }, + ) + +%> diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html new file mode 100644 index 000000000..dc388db2e --- /dev/null +++ b/httemplate/search/reg_code.html @@ -0,0 +1,37 @@ +% +% +%my $agentnum = $cgi->param('agentnum'); +%$agentnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +%$agentnum = $1; +%my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); +% +%my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum"; +% +% +<% include( 'elements/search.html', + 'title' => 'Unused Registration Codes for '. + $agent->agent, + 'name' => 'registration codes', + 'query' => { 'table' => 'reg_code', + 'hashref' => { 'agentnum' => $agentnum, }, + }, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ qw(Code Packages) ], + 'fields' => [ + 'code', + sub { + map { + qq!<A HREF="${p}edit/part_pkg.cgi?!. $_->pkgpart. '">'. + $_->pkg. ' - '. $_->comment. + '</A><BR>' + } $_[0]->part_pkg + }, + ], + 'links' => [ + '', + #$plink, + '', + ], + ) +%> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html new file mode 100644 index 000000000..c480c05f1 --- /dev/null +++ b/httemplate/search/report_cdr.html @@ -0,0 +1,12 @@ +<% include('/elements/header.html', 'Call Detail Record Search' ) %> + +<FORM ACTION="cdr.html" METHOD="GET"> +Status: <SELECT NAME="freesidestatus"> + <OPTION VALUE="">(all) + <OPTION VALUE="NULL">unprocessed + <OPTION VALUE="done">processed +</SELECT><BR> +<INPUT TYPE="submit" VALUE="Search Call Detail Records"> + +<% include('/elements/footer.html') %> + diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html new file mode 100644 index 000000000..ec57d2094 --- /dev/null +++ b/httemplate/search/report_cust_bill.html @@ -0,0 +1,28 @@ +<% include('/elements/header.html', 'Invoice report criteria' ) %> + +<FORM ACTION="cust_bill.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'Invoices for agent: ', + ) + %> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show only open invoices</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="newest_percust" VALUE="1"></TD> + <TD>Show only the single most recent invoice per-customer</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html new file mode 100644 index 000000000..96c0b5acb --- /dev/null +++ b/httemplate/search/report_cust_credit.html @@ -0,0 +1,40 @@ +<% include('/elements/header.html', 'Credit report' ) %> + +<FORM ACTION="cust_credit.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <TR> + <TD ALIGN="right">Credits by employee: </TD> +% +% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_credit") +% or die dbh->errstr; +% $sth->execute or die $sth->errstr; +% my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; +% + + + <TD><SELECT NAME="otaker"> + <OPTION VALUE="">all</OPTION> +% foreach my $otaker ( @otakers ) { + + <OPTION VALUE="<% $otaker %>"><% $otaker %></OPTION> +% } + + </SELECT> + </TD> + </TR> + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + <% include( '/elements/tr-input-beginning_ending.html' ) %> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html new file mode 100644 index 000000000..db5e65c8f --- /dev/null +++ b/httemplate/search/report_cust_main-zip.html @@ -0,0 +1,46 @@ +<% include('/elements/header.html', 'Zip code report') %> + + <FORM ACTION="cust_main-zip.html" METHOD="GET"> + + <TABLE> + + <TR> + <TD ALIGN="right">Billing or service zip</TD> + <TD> + <SELECT NAME="column"> + <OPTION VALUE="zip">Billing zip + <OPTION VALUE="ship_zip">Service zip + </SELECT> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Ignore +4 for US zip codes</TD> + <TD><INPUT TYPE="checkbox" NAME="ignore_plus4" VALUE="yes" CHECKED> </TD> + </TR> + + <TR> + <TD ALIGN="right">Show customers with status:</TD> + <TD> + <SELECT NAME="status"> + <OPTION VALUE="">all + <OPTION VALUE="prospect">prospect (no packages ever) + <OPTION SELECTED VALUE="uncancel">all except cancelled + <OPTION VALUE="active">active recurring packages + <OPTION VALUE="susp">suspended + <OPTION VALUE="cancel">cancelled + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + </BODY> +</HTML> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html new file mode 100644 index 000000000..a3e43f80a --- /dev/null +++ b/httemplate/search/report_cust_pay.html @@ -0,0 +1,66 @@ +<% include('/elements/header.html', 'Payment report' ) %> + +<FORM ACTION="cust_pay.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby" onChange="payby_changed(this)"> + <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="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + <OPTION VALUE="BILL">check</OPTION> + <OPTION VALUE="PREP">prepaid card</OPTION> + <OPTION VALUE="CASH">cash</OPTION> + <OPTION VALUE="WEST">Western Union</OPTION> + <OPTION VALUE="MCRD">manual credit card</OPTION> + </SELECT> + </TD> + </TR> + + <SCRIPT TYPE="text/javascript"> + + function payby_changed(what) { + if ( what.options[what.selectedIndex].value == 'BILL' ) { + document.getElementById('checkno_caption').style.color = '#000000'; + what.form.payinfo.disabled = false; + what.form.payinfo.style.backgroundColor = '#ffffff'; + } else { + document.getElementById('checkno_caption').style.color = '#bbbbbb'; + what.form.payinfo.disabled = true; + what.form.payinfo.style.backgroundColor = '#dddddd'; + } + } + + </SCRIPT> + + <TR> + <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD> + <TD> + <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd"> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_pay_batch.html b/httemplate/search/report_cust_pay_batch.html new file mode 100644 index 000000000..0dc4bc1b7 --- /dev/null +++ b/httemplate/search/report_cust_pay_batch.html @@ -0,0 +1,37 @@ +<% include('/elements/header.html', 'Batch payment report' ) %> + +<FORM ACTION="cust_pay_batch.cgi" METHOD="GET"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="dcln" VALUE="1" CHECKED></TD> + <TD>Include approved items</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html new file mode 100755 index 000000000..8fabf107a --- /dev/null +++ b/httemplate/search/report_cust_pkg.html @@ -0,0 +1,48 @@ +<% include('/elements/header.html', 'Package Report' ) %> + +<FORM ACTION="cust_pkg.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="bill"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH> + </TR> + <% include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + ) + %> + <% include( '/elements/tr-select-cust_pkg-status.html' ) %> + <% include( '/elements/tr-select-pkg_class.html', '', + 'pre_options' => [ '0' => 'all' ], + 'empty_label' => '(empty class)', + ) + %> +% #include( '/elements/tr-selectmultiple-part_pkg.html' ) + + <TR> + <TD ALIGN="right" VALIGN="center">Next bill date</TD> + <TD> + <TABLE> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + </TABLE> + </TD> + </TR> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2> </TH> + </TR> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH> + </TR> + <% include( '/elements/tr-select-cust-fields.html' ) %> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi new file mode 100644 index 000000000..241e8a008 --- /dev/null +++ b/httemplate/search/report_prepaid_income.cgi @@ -0,0 +1,87 @@ +<!-- mason kludge --> +% +% +% #doesn't yet deal with daily/weekly packages +% +% #needs to be re-written in sql for efficiency +% +% my $time = time; +% +% my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time; +% $now =~ /^(\d+)$/ or die "unparsable date?"; +% $now = $1; +% +% my( $total, $total_legacy ) = ( 0, 0 ); +% +% my @cust_bill_pkg = +% grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ } +% qsearch( 'cust_bill_pkg', { +% 'recur' => { op=>'!=', value=>0 }, +% 'edate' => { op=>'>', value=>$now }, +% }, ); +% +% my @cust_pkg = +% grep { $_->part_pkg->recur != 0 +% && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/ +% } +% qsearch ( 'cust_pkg', { +% 'bill' => { op=>'>', value=>$now } +% } ); +% +% foreach my $cust_bill_pkg ( @cust_bill_pkg) { +% my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate; +% +% my $elapsed = $now - $cust_bill_pkg->sdate; +% $elapsed = 0 if $elapsed < 0; +% +% my $remaining = 1 - $elapsed/$period; +% +% my $unearned = $remaining * $cust_bill_pkg->recur; +% $total += $unearned; +% +% } +% +% foreach my $cust_pkg ( @cust_pkg ) { +% my $period = $cust_pkg->bill - $cust_pkg->last_bill; +% +% my $elapsed = $now - $cust_pkg->last_bill; +% $elapsed = 0 if $elapsed < 0; +% +% my $remaining = 1 - $elapsed/$period; +% +% my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy +% $total_legacy += $unearned; +% +% } +% +% $total = sprintf('%.2f', $total); +% $total_legacy = sprintf('%.2f', $total_legacy); +% +% + + +<% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report', + menubar( 'Main Menu'=>$p, ) ) %> +<% table() %> + <TR> + <TH>Actual Unearned Revenue</TH> + <TH>Legacy Unearned Revenue</TH> + </TR> + <TR> + <TD ALIGN="right">$<% $total %> + <TD ALIGN="right"> + <% $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%> + </TD> + </TR> + +</TABLE> +<BR> +Actual unearned revenue is the amount of unearned revenue Freeside has +actually invoiced for packages with longer-than monthly terms. +<BR><BR> +Legacy unearned revenue is the amount of unearned revenue represented by +customer packages. This number may be larger than actual unearned +revenue if you have imported longer-than monthly customer packages from +a previous billing system. +</BODY> +</HTML> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html new file mode 100644 index 000000000..305441db7 --- /dev/null +++ b/httemplate/search/report_prepaid_income.html @@ -0,0 +1,37 @@ +<% include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report', + '', + '', + '<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + ' +) %> + + <FORM ACTION="report_prepaid_income.cgi" METHOD="GET"> + <TABLE> + <TR> + <TD>Prepaid income (unearned revenue) as of </TD> + <TD> + <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now"> + <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> + </TR> + <TR> + <TD> + </TD> + <TD><i>m/d/y</i></TD> + </TR> + </TABLE> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "date_text", + ifFormat: "%m/%d/%Y", + button: "date_button", + align: "BR" + }); +</SCRIPT> + +<INPUT TYPE="submit" VALUE="Generate report"> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi new file mode 100755 index 000000000..46b3ca85e --- /dev/null +++ b/httemplate/search/report_receivables.cgi @@ -0,0 +1,217 @@ +% +% +% sub owed { +% my($start, $end, %opt) = @_; +% +% my @where = (); +% +% #handle start and end ranges +% +% #24h * 60m * 60s +% push @where, "cust_bill._date <= extract(epoch from now())-". +% ($start * 86400) +% if $start; +% +% push @where, "cust_bill._date > extract(epoch from now()) - ". +% ($end * 86400) +% if $end; +% +% #handle 'cust' option +% +% push @where, "cust_main.custnum = cust_bill.custnum" +% if $opt{'cust'}; +% +% #handle 'agentnum' option +% my $join = ''; +% if ( $opt{'agentnum'} ) { +% $join = 'LEFT JOIN cust_main USING ( custnum )'; +% push @where, "agentnum = '$opt{'agentnum'}'"; +% } +% +% my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; +% +% my $as = $opt{'noas'} ? '' : "as owed_${start}_$end"; +% +% 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 +% ) +% +% ) +%END +% +% "coalesce( ( select $charged from cust_bill $join $where ) ,0 ) $as"; +% +% } +% +% my @ranges = ( +% [ 0, 30 ], +% [ 30, 60 ], +% [ 60, 90 ], +% [ 90, 0 ], +% [ 0, 0 ], +% ); +% +% my $owed_cols = join(',', map owed( @$_, 'cust'=>1 ), @ranges ); +% +% my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql; +% +% my $active_sql = FS::cust_pkg->active_sql; +% my $inactive_sql = FS::cust_pkg->inactive_sql; +% my $suspended_sql = FS::cust_pkg->suspended_sql; +% my $cancelled_sql = FS::cust_pkg->cancelled_sql; +% +% my $packages_cols = <<END; +% ( $select_count_pkgs ) AS num_pkgs_sql, +% ( $select_count_pkgs AND $active_sql ) AS active_pkgs, +% ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs, +% ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs, +% ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs +%END +% +% my $where = "where ". owed(0, 0, 'cust'=>1, 'noas'=>1). " > 0"; +% +% my $agentnum = ''; +% if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% $agentnum = $1; +% $where .= " AND agentnum = '$agentnum' "; +% } +% +% #here is the agent virtualization +% $where .= ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% my $count_sql = "select count(*) from cust_main $where"; +% +% my $sql_query = { +% 'table' => 'cust_main', +% 'hashref' => {}, +% 'select' => "*, $owed_cols, $packages_cols", +% 'extra_sql' => "$where order by coalesce(lower(company), ''), lower(last)", +% }; +% +% my $total_sql = "select ". +% join(',', map owed( @$_, 'agentnum'=>$agentnum ), @ranges ); +% +% my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; +% $total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr; +% my $row = $total_sth->fetchrow_hashref(); +% +% my $conf = new FS::Conf; +% my $money_char = $conf->config('money_char') || '$'; +% +% my $align = join('', map { /#/ ? 'r' : 'l' } FS::UI::Web::cust_header() ). +% 'crrrrr'; +% +% my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; +% +% my $status_statuscol = sub { +% #conceptual false laziness with cust_main::status... +% my $row = shift; +% +% my $status = 'unknown'; +% if ( $row->num_pkgs_sql == 0 ) { +% $status = 'prospect'; +% } elsif ( $row->active_pkgs > 0 ) { +% $status = 'active'; +% } elsif ( $row->inactive_pkgs > 0 ) { +% $status = 'inactive'; +% } elsif ( $row->suspended_pkgs > 0 ) { +% $status = 'suspended'; +% } elsif ( $row->cancelled_pkgs > 0 ) { +% $status = 'cancelled' +% } +% +% ( ucfirst($status), $FS::cust_main::statuscolor{$status} ); +% }; +% +% +% +<% include( 'elements/search.html', + 'title' => 'Accounts Receivable Aging Summary', + 'name' => 'customers', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ + FS::UI::Web::cust_header(), + 'Status', # (me)', + #'Status', # (cust_main)', + '0-30', + '30-60', + '60-90', + '90+', + 'Total', + ], + 'footer' => [ + 'Total', + ( map '', + ( 1 .. + scalar(FS::UI::Web::cust_header()-1) + ) + ), + '', + #'', + sprintf( $money_char.'%.2f', + $row->{'owed_0_30'} ), + sprintf( $money_char.'%.2f', + $row->{'owed_30_60'} ), + sprintf( $money_char.'%.2f', + $row->{'owed_60_90'} ), + sprintf( $money_char.'%.2f', + $row->{'owed_90_0'} ), + sprintf( '<b>'. $money_char.'%.2f'. '</b>', + $row->{'owed_0_0'} ), + ], + 'fields' => [ + \&FS::UI::Web::cust_fields, + sub { ( &{$status_statuscol}(shift) )[0] }, + #sub { ucfirst(shift->status) }, + sub { sprintf( $money_char.'%.2f', + shift->get('owed_0_30') ) }, + sub { sprintf( $money_char.'%.2f', + shift->get('owed_30_60') ) }, + sub { sprintf( $money_char.'%.2f', + shift->get('owed_60_90') ) }, + sub { sprintf( $money_char.'%.2f', + shift->get('owed_90_0') ) }, + sub { sprintf( $money_char.'%.2f', + shift->get('owed_0_0') ) }, + ], + 'links' => [ + ( map $clink, FS::UI::Web::cust_header() ), + '', + #'', + '', + '', + '', + '', + '', + ], + #'align' => 'rlccrrrrr', + 'align' => $align, + #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ], + #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ], + 'size' => [ ( map '', FS::UI::Web::cust_header() ), + '-1', '', '', '', '', '', ], + 'style' => [ ( map '', FS::UI::Web::cust_header() ), + 'b', '', '', '', '', 'b', ], + 'color' => [ + ( map '', FS::UI::Web::cust_header() ), + sub { ( &{$status_statuscol}(shift) )[1] }, + #sub { shift->statuscolor; }, + '', + '', + '', + '', + '', + ], + + ) +%> diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html new file mode 100755 index 000000000..1ae4b8e6f --- /dev/null +++ b/httemplate/search/report_receivables.html @@ -0,0 +1,16 @@ +<% include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %> + + <FORM ACTION="report_receivables.cgi" METHOD="GET"> + + <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + </TABLE> + + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + + </BODY> +</HTML> + diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi new file mode 100755 index 000000000..569e6e79a --- /dev/null +++ b/httemplate/search/report_tax.cgi @@ -0,0 +1,539 @@ +% +% +%my $conf = new FS::Conf; +%my $money_char = $conf->config('money_char') || '$'; +% +%my $user = getotaker; +% +%my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +% +%my $join_cust = " +% JOIN cust_bill USING ( invnum ) +% LEFT JOIN cust_main USING ( custnum ) +%"; +%my $from_join_cust = " +% FROM cust_bill_pkg +% $join_cust +%"; +%my $join_pkg = " +% LEFT JOIN cust_pkg USING ( pkgnum ) +% LEFT JOIN part_pkg USING ( pkgpart ) +%"; +% +%my $where = "WHERE _date >= $beginning AND _date <= $ending "; +%my @base_param = qw( county county state state country ); +%if ( $conf->exists('tax-ship_address') ) { +% +% $where .= " +% AND ( ( ( ship_last IS NULL OR ship_last = '' ) +% AND ( county = ? OR ? = '' ) +% AND ( state = ? OR ? = '' ) +% AND country = ? +% ) +% OR ( ship_last IS NOT NULL AND ship_last != '' +% AND ( ship_county = ? OR ? = '' ) +% AND ( ship_state = ? OR ? = '' ) +% AND ship_country = ? +% ) +% ) +% "; +% # AND payby != 'COMP' +% +% push @base_param, @base_param; +% +%} else { +% +% $where .= " +% AND ( county = ? OR ? = '' ) +% AND ( state = ? OR ? = '' ) +% AND country = ? +% "; +% # AND payby != 'COMP' +% +%} +% +%my $agentname = ''; +%if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +% my $agent = qsearchs('agent', { 'agentnum' => $1 } ); +% die "agent not found" unless $agent; +% $agentname = $agent->agent; +% $where .= ' AND agentnum = '. $agent->agentnum; +%} +% +%my $gotcust = " +% WHERE 0 < ( SELECT COUNT(*) FROM cust_main +%"; +%if ( $conf->exists('tax-ship_address') ) { +% +% $gotcust .= " +% WHERE +% +% ( cust_main_county.country = cust_main.country +% OR cust_main_county.country = cust_main.ship_country +% ) +% +% AND +% +% ( +% +% ( ( ship_last IS NULL OR ship_last = '' ) +% AND ( cust_main_county.country = cust_main.country ) +% AND ( cust_main_county.state = cust_main.state +% OR cust_main_county.state = '' +% OR cust_main_county.state IS NULL ) +% AND ( cust_main_county.county = cust_main.county +% OR cust_main_county.county = '' +% OR cust_main_county.county IS NULL ) +% ) +% +% OR +% +% ( ship_last IS NOT NULL AND ship_last != '' +% AND ( cust_main_county.country = cust_main.ship_country ) +% AND ( cust_main_county.state = cust_main.ship_state +% OR cust_main_county.state = '' +% OR cust_main_county.state IS NULL ) +% AND ( cust_main_county.county = cust_main.ship_county +% OR cust_main_county.county = '' +% OR cust_main_county.county IS NULL ) +% ) +% +% ) +% +% LIMIT 1 +% ) +% "; +% +%} else { +% +% $gotcust .= " +% WHERE ( cust_main.county = cust_main_county.county +% OR cust_main_county.county = '' +% OR cust_main_county.county IS NULL ) +% AND ( cust_main.state = cust_main_county.state +% OR cust_main_county.state = '' +% OR cust_main_county.state IS NULL ) +% AND ( cust_main.country = cust_main_county.country ) +% LIMIT 1 +% ) +% "; +% +%} +% +%my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 ); +%my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 ); +%my $out = 'Out of taxable region(s)'; +%my %regions = (); +%foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { +% #warn $r->county. ' '. $r->state. ' '. $r->country. "\n"; +% +% my $label = getlabel($r); +% $regions{$label}->{'label'} = $label; +% $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) ); +% +% my @param = @base_param; +% my $mywhere = $where; +% +% if ( $r->taxclass ) { +% $mywhere .= " AND taxclass = ? "; +% push @param, 'taxclass'; +% $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass +% if $cgi->param('show_taxclasses'); +% } +% +% my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' "; +% +%# my $label = getlabel($r); +%# $regions{$label}->{'label'} = $label; +% +% my $nottax = 'pkgnum != 0'; +% +% ## calculate total for this region +% +% my $t = scalar_sql($r, \@param, +% "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax" +% ); +% $total += $t; +% $regions{$label}->{'total'} += $t; +% +% ## calculate customer-exemption for this region +% +%## my $taxable = $t; +% +%# my($taxable, $x_cust) = (0, 0); +%# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } +%# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { +%# $taxable += scalar_sql($r, \@param, +%# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" +%# ); +%# +%# $x_cust += scalar_sql($r, \@param, +%# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" +%# ); +%# } +% +% my $x_cust = scalar_sql($r, \@param, +% "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) +% $fromwhere AND $nottax AND tax = 'Y' " +% ); +% +% $exempt_cust += $x_cust; +% $regions{$label}->{'exempt_cust'} += $x_cust; +% +% ## calculate package-exemption for this region +% +% my $x_pkg = scalar_sql($r, \@param, +% "SELECT SUM( +% ( CASE WHEN part_pkg.setuptax = 'Y' +% THEN cust_bill_pkg.setup +% ELSE 0 +% END +% ) +% + +% ( CASE WHEN part_pkg.recurtax = 'Y' +% THEN cust_bill_pkg.recur +% ELSE 0 +% END +% ) +% ) +% $fromwhere +% AND $nottax +% AND ( +% ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) +% OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) +% ) +% AND ( tax != 'Y' OR tax IS NULL ) +% " +% ); +% $exempt_pkg += $x_pkg; +% $regions{$label}->{'exempt_pkg'} += $x_pkg; +% +% ## calculate monthly exemption (texas tax) for this region +% +% # count up all the cust_tax_exempt_pkg records associated with +% # the actual line items. +% +% my $x_monthly = scalar_sql($r, \@param, +% "SELECT SUM(amount) +% FROM cust_tax_exempt_pkg +% JOIN cust_bill_pkg USING ( billpkgnum ) +% $join_cust $join_pkg +% $mywhere" +% ); +%# if ( $x_monthly ) { +%# #warn $r->taxnum(). ": $x_monthly\n"; +%# $taxable -= $x_monthly; +%# } +% +% $exempt_monthly += $x_monthly; +% $regions{$label}->{'exempt_monthly'} += $x_monthly; +% +% my $taxable = $t - $x_cust - $x_pkg - $x_monthly; +% +% $tot_taxable += $taxable; +% $regions{$label}->{'taxable'} += $taxable; +% +% $owed += $taxable * ($r->tax/100); +% $regions{$label}->{'owed'} += $taxable * ($r->tax/100); +% +% if ( defined($regions{$label}->{'rate'}) +% && $regions{$label}->{'rate'} != $r->tax.'%' ) { +% $regions{$label}->{'rate'} = 'variable'; +% } else { +% $regions{$label}->{'rate'} = $r->tax.'%'; +% } +% +%} +% +%my $taxwhere = "$from_join_cust $where AND payby != 'COMP' "; +%my @taxparam = @base_param; +%my %base_regions = (); +%#foreach my $label ( keys %regions ) { +%foreach my $r ( +% qsearch( 'cust_main_county', +% {}, +% 'DISTINCT ON (country, state, county, taxname) *', +% $gotcust +% ) +%) { +% +% #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n"; +% +% my $label = getlabel($r); +% +% #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; +% #my @param = @base_param; +% +% #match itemdesc if necessary! +% my $named_tax = +% $r->taxname +% ? 'AND itemdesc = '. dbh->quote($r->taxname) +% : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; +% my $x = scalar_sql($r, \@taxparam, +% "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $taxwhere ". +% "AND pkgnum = 0 $named_tax", +% ); +% $tax += $x; +% $regions{$label}->{'tax'} += $x; +% +% if ( $cgi->param('show_taxclasses') ) { +% my $base_label = getlabel($r, 'no_taxclass'=>1 ); +% $base_regions{$base_label}->{'label'} = $base_label; +% $base_regions{$base_label}->{'url_param'} = +% join(';', map "$_=".$r->$_(), qw( county state country ) ); +% $base_regions{$base_label}->{'tax'} += $x; +% } +% +%} +% +%#ordering +%my @regions = +% map $regions{$_}, +% sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } +% keys %regions; +% +%my @base_regions = +% map $base_regions{$_}, +% sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } +% keys %base_regions; +% +%push @regions, { +% 'label' => 'Total', +% 'url_param' => '', +% 'total' => $total, +% 'exempt_cust' => $exempt_cust, +% 'exempt_pkg' => $exempt_pkg, +% 'exempt_monthly' => $exempt_monthly, +% 'taxable' => $tot_taxable, +% 'rate' => '', +% 'owed' => $owed, +% 'tax' => $tax, +%}; +% +%#-- +% +%sub getlabel { +% my $r = shift; +% my %opt = @_; +% +% my $label; +% if ( +% $r->tax == 0 +% && ! scalar( qsearch('cust_main_county', { 'state' => $r->state, +% 'county' => $r->county, +% 'country' => $r->country, +% 'tax' => { op=>'>', value=>0 }, +% } +% ) +% ) +% +% ) { +% #kludge to avoid "will not stay shared" warning +% my $out = 'Out of taxable region(s)'; +% $label = $out; +% } elsif ( $r->taxname ) { +% $label = $r->taxname; +%# $regions{$label}->{'taxname'} = $label; +%# push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country ); +% } else { +% $label = $r->country; +% $label = $r->state.", $label" if $r->state; +% $label = $r->county." county, $label" if $r->county; +% $label = "$label (". $r->taxclass. ")" +% if $r->taxclass +% && $cgi->param('show_taxclasses') +% && ! $opt{'no_taxclass'}; +% #$label = $r->taxname. " ($label)" if $r->taxname; +% } +% return $label; +%} +% +%#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up +%#to FS::Report or FS::Record or who the fuck knows where) +%sub scalar_sql { +% my( $r, $param, $sql ) = @_; +% #warn "$sql\n"; +% my $sth = dbh->prepare($sql) or die dbh->errstr; +% $sth->execute( map $r->$_(), @$param ) +% or die "Unexpected error executing statement $sql: ". $sth->errstr; +% $sth->fetchrow_arrayref->[0] || 0; +%} +% +% +% +%my $dateagentlink = "begin=$beginning;end=$ending"; +%$dateagentlink .= ';agentnum='. $cgi->param('agentnum') +% if length($agentname); +%my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink"; +%my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; +% + + +<% include("/elements/header.html", "$agentname Sales Tax Report - ". + ( $beginning + ? time2str('%h %o %Y ', $beginning ) + : '' + ). + 'through '. + ( $ending == 4294967295 + ? 'now' + : time2str('%h %o %Y', $ending ) + ), + menubar( 'Main Menu'=>$p, ) + ) +%> + +<% include('/elements/table-grid.html') %> + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH> +% unless ( $cgi->param('show_taxclasses') ) { + + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH> +% } + + </TR> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH> + </TR> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% foreach my $region ( @regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% } +% +% +% +% +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1"><% $money_char %><% sprintf('%.2f', $region->{'total'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_cust'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_pkg'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $exemptlink. $link %>"><% $money_char %><% sprintf('%.2f', $region->{'exempt_monthly'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'taxable'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $region->{'rate'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'owed'} ) %> + </TD> +% unless ( $cgi->param('show_taxclasses') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> +% } + + </TR> +% } + + +</TABLE> +% if ( $cgi->param('show_taxclasses') ) { + + + <BR> + <% include('/elements/table-grid.html') %> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH> + </TR> +% #some false laziness w/above +% $bgcolor1 = '#eeeeee'; +% $bgcolor2 = '#ffffff'; +% foreach my $region ( @base_regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% #if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% #} +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> + </TR> +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">Total</TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax ) %></A> + </TD> + </TR> + + </TABLE> +% } + + +</BODY> +</HTML> + + diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html new file mode 100755 index 000000000..6e78d354a --- /dev/null +++ b/httemplate/search/report_tax.html @@ -0,0 +1,36 @@ +<% include('/elements/header.html', 'Tax Report' ) %> + +<FORM ACTION="report_tax.cgi" METHOD="GET"> + +<TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> +% my $conf = new FS::Conf; +% if ( $conf->exists('enable_taxclasses') ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD> + <TD>Show tax classes</TD> + </TR> +% } +% my @pkg_class = qsearch('pkg_class', {}); +% if ( @pkg_class ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD> + <TD>Show package classes</TD> + </TR> +% } + + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html new file mode 100644 index 000000000..681a95d60 --- /dev/null +++ b/httemplate/search/sql.html @@ -0,0 +1,7 @@ +<% include( 'elements/search.html', + 'title' => 'Query Results', + 'name' => 'rows', + 'query' => 'SELECT '. ( $cgi->param('sql') + || eidiot('Empty query') ), + ) +%> diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi new file mode 100644 index 000000000..486b94d94 --- /dev/null +++ b/httemplate/search/sqlradius.cgi @@ -0,0 +1,308 @@ +<% include( '/elements/header.html', 'RADIUS Sessions', + include('/elements/menubar.html', + 'Main menu' => $p, # popurl(2), + ), + + ) +%> + +% ### +% # and finally, display the thing +% ### +% +% foreach my $part_export ( +% #grep $_->can('usage_sessions'), qsearch( 'part_export' ) +% qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ), +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ) +% ) { +% %user2svc_acct = (); +% +% my $efields = tie my %efields, 'Tie::IxHash', %fields; +% delete $efields{'framedipaddress'} if $part_export->option('hide_ip'); +% if ( $part_export->option('hide_data') ) { +% delete $efields{$_} foreach qw(acctinputoctets acctoutputoctets); +% } +% if ( $part_export->option('show_called_station') ) { +% $efields->Splice(1, 0, +% 'calledstationid' => { +% 'name' => 'Destination', +% 'attrib' => 'Called-Station-ID', +% 'fmt' => +% sub { length($_[0]) ? shift : ' '; }, +% 'align' => 'left', +% }, +% ); +% } +% +% + + <% $part_export->exporttype %> to <% $part_export->machine %><BR> + <% include( '/elements/table-grid.html' ) %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; + + <TR> +% foreach my $field ( keys %efields ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"> + <% $efields{$field}->{name} %><BR> + <FONT SIZE=-2><% $efields{$field}->{attrib} %></FONT> + </TH> + +% } + </TR> + +% foreach my $session ( +% @{ $part_export->usage_sessions( +% $beginning, $ending, $cgi_svc_acct, $ip, $prefix, ) } +% ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> +% foreach my $field ( keys %efields ) { +% my $html = &{ $efields{$field}->{fmt} }( $session->{$field}, +% $session, +% $part_export, +% ); +% my $class = ( $html =~ /<TABLE/ ? 'inv' : 'grid' ); + + <TD CLASS="<%$class%>" BGCOLOR="<% $bgcolor %>" ALIGN="<% $efields{$field}->{align} %>"> + <% $html %> + </TD> +% } + </TR> + +% } + +</TABLE> +<BR><BR> + +% } + + +<%init> +### +# parse cgi params +### + +#sort of false laziness w/cust_pay.cgi +my $beginning = ''; +my $ending = ''; +if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $beginning = str2time($1); +} +if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $ending = str2time($1); # + 86399; +} +if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) { + $beginning = $1; +} +if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) { + $ending = $1; +} + +my $cgi_svc_acct = ''; +if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } ); +} elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) { + my %search = { 'username' => $1 }; + my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; + } + $cgi_svc_acct = qsearchs( 'svc_acct', \%search ) + if keys %search; +} elsif ( $cgi->param('username') =~ /^(.+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } ); +} + +my $ip = ''; +if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) { + $ip = $1; +} + +my $prefix = $cgi->param('prefix'); +$prefix =~ s/\D//g; +if ( $prefix =~ /^(\d+)$/ ) { + $prefix = $1; + $prefix = "011$prefix" unless $prefix =~ /^1/; +} else { + $prefix = ''; +} + +### +# field formatting subroutines +### + +my %user2svc_acct = (); +my $user_format = sub { + my ( $user, $session, $part_export ) = @_; + + my $svc_acct = ''; + if ( exists $user2svc_acct{$user} ) { + $svc_acct = $user2svc_acct{$user}; + } else { + my %search = (); + if ( $part_export->exporttype eq 'sqlradius_withdomain' ) { + my $domain; + if ( $user =~ /^([^@]+)\@([^@]+)$/ ) { + $search{'username'} = $1; + $domain = $2; + } else { + $search{'username'} = $user; + $domain = $session->{'realm'}; + } + my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; + } + } elsif ( $part_export->exporttype eq 'sqlradius' ) { + $search{'username'} = $user; + } else { + die 'unknown export type '. $part_export->exporttype. + " for $part_export\n"; + } + if ( keys %search ) { + my @svc_acct = + grep { qsearchs( 'export_svc', { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $_->cust_svc->svcpart, + } ) + } qsearch( 'svc_acct', \%search ); + if ( @svc_acct ) { + warn 'multiple svc_acct records for user $user found; '. + 'using first arbitrarily' + if scalar(@svc_acct) > 1; + $user2svc_acct{$user} = $svc_acct = shift @svc_acct; + } + } + } + + if ( $svc_acct ) { + my $svcnum = $svc_acct->svcnum; + qq(<A HREF="${p}view/svc_acct.cgi?$svcnum"><B>$user</B></A>); + } else { + "<B>$user</B>"; + } + +}; + +my $customer_format = sub { + my( $unused, $session ) = @_; + return ' ' unless exists $user2svc_acct{$session->{'username'}}; + my $svc_acct = $user2svc_acct{$session->{'username'}}; + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + return ' ' unless $cust_pkg; + my $cust_main = $cust_pkg->cust_main; + + qq!<A HREF="${p}view/cust_main.cgi?!. $cust_main->custnum. '">'. + $cust_pkg->cust_main->name. '</A>'; +}; + +my $time_format = sub { + my $time = shift; + return ' ' if $time == 0; + my $pretty = time2str('%T%P %a %b %o %Y', $time ); + $pretty =~ s/ (\d)(st|dn|rd|th)/$1$2/; + $pretty; +}; + +my $duration_format = sub { + my $seconds = shift; + my $hour = int($seconds/3600); + my $min = int( ($seconds%3600) / 60 ); + my $sec = $seconds%60; + '<TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0>'. + '<TR><TD CLASS="inv" ALIGN="right">'. + ( $hour ? "<B>$hour</B>h" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + ( ( $hour || $min ) ? "<B>$min</B>m" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + "<B>$sec</B>s". + '</TD></TR></TABLE>'; +}; + +my $octets_format = sub { + my $octets = shift; + my $megs = $octets / 1048576; + sprintf('<B>%.3f</B> megs', $megs); + #my $gigs = $octets / 1073741824 + #sprintf('<B>%.3f</B> gigabytes', $gigs); +}; + +### +# the fields +### + +tie my %fields, 'Tie::IxHash', + 'username' => { + name => 'User', + attrib => 'UserName', + fmt => $user_format, + align => 'left', + }, + 'realm' => { + name => 'Realm', + attrib => 'Realm', + align => 'left', + }, + 'dummy' => { + name => 'Customer', + attrib => '', + fmt => $customer_format, + align => 'left', + }, + 'framedipaddress' => { + name => 'IP Address', + attrib => 'Framed-IP-Address', + fmt => sub { my $ip = shift; + length($ip) ? $ip : ' '; + }, + align => 'right', + }, + 'acctstarttime' => { + name => 'Start time', + attrib => 'Acct-Start-Time', + fmt => $time_format, + align => 'left', + }, + 'acctstoptime' => { + name => 'End time', + attrib => 'Acct-Stop-Time', + fmt => $time_format, + align => 'left', + }, + 'acctsessiontime' => { + name => 'Duration', + attrib => 'Acct-Session-Time', + fmt => $duration_format, + align => 'right', + }, + 'acctinputoctets' => { + name => 'Upload', # (from user)', + attrib => 'Acct-Input-Octets', + fmt => $octets_format, + align => 'right', + }, + 'acctoutputoctets' => { + name => 'Download', # (to user)', + attrib => 'Acct-Output-Octets', + fmt => $octets_format, + align => 'right', + }, +; +$fields{$_}->{fmt} ||= sub { length($_[0]) ? shift : ' '; } + foreach keys %fields; + +</%init> diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html new file mode 100644 index 000000000..1fd50da8d --- /dev/null +++ b/httemplate/search/sqlradius.html @@ -0,0 +1,53 @@ +<% include( '/elements/header.html', 'Search RADIUS sessions' ) %> + +<FORM NAME="OneTrueForm" ACTION="sqlradius.cgi" METHOD="GET"> +% #include( '/elements/table.html' ) + +<% ntable('#cccccc') %> +<TR> + <TD ALIGN="right">Username: </TD> + <TD><INPUT TYPE="text" NAME="username"></TD> +</TR> +<TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all users)</I></FONT></TD> +</TR> +% my @part_export = qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ); +% push @part_export, +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ); +% +% if ( grep { ! $_->option('hide_ip') } @part_export ) { + + <TR> + <TD ALIGN="right">IP address: </TD> + <TD><INPUT TYPE="text" NAME="ip"></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all IPs)</I></FONT></TD> + </TR> +% } +% if ( grep { $_->option('show_called_station') } @part_export ) { + + <TR> + <TD ALIGN="right">Destination prefix:</TD> + <TD><INPUT TYPE="text" NAME="prefix"></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(country code or country code and prefix)</I></FONT></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all destinations)</I></FONT></TD> + </TR> +% } + + +<% include( '/elements/tr-input-beginning_ending.html', 'input_time'=>1 ) %> + +</TABLE> +<BR><INPUT TYPE="submit" VALUE="View sessions"> +</FORM> + +<% include('/elements/footer.html') %> diff --git a/httemplate/search/svc_Smart.html b/httemplate/search/svc_Smart.html new file mode 100644 index 000000000..706471ba0 --- /dev/null +++ b/httemplate/search/svc_Smart.html @@ -0,0 +1,28 @@ +% if ( $cgi->param('search_svc') =~ /\.[a-z]{2,8}$/i +% && $cgi->param('search_svc') !~ /\@/ +% ) +% { + +% # looks (enough) like a domain + <% $cgi->redirect('svc_domain.cgi?domain='. uri_escape( $cgi->param('search_svc') ) ) %> + +% } elsif ( $cgi->param('search_svc') =~ /\w/ ) { +% +% if ( $cgi->param('search_svc') =~ /^(.+)\@(.+)$/ ) { + +% #looks (enough) like a username@domain + <% $cgi->redirect('svc_acct.cgi?username_type=Exact;username='. uri_escape($1). ';domain='. uri_escape($2) ) %> + +% } else { + +% #looks (enough) like a username + <% $cgi->redirect('svc_acct.cgi?username_type=Exact;username='. uri_escape( $cgi->param('search_svc') ) ) %> + +% } + +% } else { + + <% include('/elements/header.html', 'Unrecognized service string') %> + <% include('/elements/footer.html') %> + +% } diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi new file mode 100755 index 000000000..2a1414bea --- /dev/null +++ b/httemplate/search/svc_acct.cgi @@ -0,0 +1,156 @@ +% +% +%my $orderby = 'ORDER BY svcnum'; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +% +%my @extra_sql = (); +%if ( $query =~ /^UN_(.*)$/ ) { +% $query = $1; +% push @extra_sql, 'pkgnum IS NULL'; +%} +% +% if ( $cgi->param('domain') ) { +% my $svc_domain = +% qsearchs('svc_domain', { 'domain' => $cgi->param('domain') } ); +% unless ( $svc_domain ) { +% #it would be nice if this looked more like the other "not found" +% #errors, but this will do for now. +% eidiot "Domain ". $cgi->param('domain'). " not found at all"; +% } else { +% push @extra_sql, 'domsvc = '. $svc_domain->svcnum; +% } +% } +% +%if ( $query eq 'svcnum' ) { +% #$orderby = "ORDER BY svcnum"; +%} elsif ( $query eq 'username' ) { +% $orderby = "ORDER BY LOWER(username)"; +%} elsif ( $query eq 'uid' ) { +% $orderby = "ORDER BY uid"; +% push @extra_sql, "uid IS NOT NULL"; +%} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) { +% push @extra_sql, "popnum = $1"; +% $orderby = "ORDER BY LOWER(username)"; +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% push @extra_sql, "svcpart = $1"; +% $orderby = "ORDER BY uid"; +% #$orderby = "ORDER BY svcnum"; +%} else { +% $orderby = "ORDER BY uid"; +% +% my @username_sql; +% +% my %username_type; +% foreach ( $cgi->param('username_type') ) { +% $username_type{$_}++; +% } +% +% $cgi->param('username') =~ /^([\w\-\.\&]+)$/; #untaint username_text +% my $username = $1; +% +% push @username_sql, "username ILIKE '$username'" +% if $username_type{'Exact'} +% || $username_type{'Fuzzy'}; +% +% push @username_sql, "username ILIKE '\%$username\%'" +% if $username_type{'Substring'} +% || $username_type{'All'}; +% +% if ( $username_type{'Fuzzy'} || $username_type{'All'} ) { +% &FS::svc_acct::check_and_rebuild_fuzzyfiles; +% my $all_username = &FS::svc_acct::all_username; +% +% my %username; +% if ( $username_type{'Fuzzy'} || $username_type{'All'} ) { +% foreach ( amatch($username, [ qw(i) ], @$all_username) ) { +% $username{$_}++; +% } +% } +% +% #if ($username_type{'Sound-alike'}) { +% #} +% +% push @username_sql, "username = '$_'" +% foreach (keys %username); +% +% } +% +% push @extra_sql, '( '. join( ' OR ', @username_sql). ' )'; +% +%} +% +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +%#here is the agent virtualization +%push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = +% scalar(@extra_sql) +% ? ' WHERE '. join(' AND ', @extra_sql ) +% : ''; +% +%my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql"; +%#if ( keys %svc_acct ) { +%# $count_query .= ' WHERE '. +%# join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}), +%# keys %svc_acct +%# ); +%#} +% +%my $sql_query = { +% 'table' => 'svc_acct', +% 'hashref' => {}, # \%svc_acct, +% 'select' => join(', ', +% 'svc_acct.*', +% 'part_svc.svc', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => "$extra_sql $orderby", +% 'addl_from' => $addl_from, +%}; +% +%my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ]; +%my $link_cust = sub { +% my $svc_acct = shift; +% if ( $svc_acct->custnum ) { +% [ "${p}view/cust_main.cgi?", 'custnum' ]; +% } else { +% ''; +% } +%}; +% +% +<% include( 'elements/search.html', + 'title' => 'Account Search Results', + 'name' => 'accounts', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Account', + 'UID', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'email', + 'uid', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + $link, + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi new file mode 100755 index 000000000..ae32ccd7e --- /dev/null +++ b/httemplate/search/svc_broadband.cgi @@ -0,0 +1,99 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +%my(@svc_broadband,$sortby); +%if ( $query eq 'svcnum' ) { +% $sortby=\*svcnum_sort; +% @svc_broadband=qsearch('svc_broadband',{}); +%} elsif ( $query eq 'blocknum' ) { +% $sortby=\*blocknum_sort; +% @svc_broadband=qsearch('svc_broadband',{}); +%} else { +% $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/; +% my($ip_addr)=$1; +% @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr}); +%} +% +%my %routerbyblock = (); +%foreach my $router (qsearch('router', {})) { +% foreach ($router->addr_block) { +% $routerbyblock{$_->blocknum} = $router; +% } +%} +% +%if ( scalar(@svc_broadband) == 1 ) { +% print $cgi->redirect(popurl(2). "view/svc_broadband.cgi?". $svc_broadband[0]->svcnum); +% #exit; +%} elsif ( scalar(@svc_broadband) == 0 ) { +% + +<!-- mason kludge --> +% +% eidiot "No matching ip address found!\n"; +%} else { +% + +<!-- mason kludge --> +% +% my($total)=scalar(@svc_broadband); +% print header("IP Address Search Results",''), <<END; +% +% $total matching broadband services found +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Service #</TH> +% <TH>Router</TH> +% <TH>IP Address</TH> +% </TR> +%END +% +% foreach my $svc_broadband ( +% sort $sortby (@svc_broadband) +% ) { +% my($svcnum,$ip_addr,$routername,$routernum)=( +% $svc_broadband->svcnum, +% $svc_broadband->ip_addr, +% $routerbyblock{$svc_broadband->blocknum}->routername, +% $routerbyblock{$svc_broadband->blocknum}->routernum, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/router.cgi?$routernum">$routername</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$ip_addr</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub blocknum_sort { +% if ($a->getfield('blocknum') == $b->getfield('blocknum')) { +% $a->getfield('ip_addr') cmp $b->getfield('ip_addr'); +% } else { +% $a->getfield('blocknum') cmp $b->getfield('blocknum'); +% } +%} +% +% +% + diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi new file mode 100755 index 000000000..85ae94a80 --- /dev/null +++ b/httemplate/search/svc_domain.cgi @@ -0,0 +1,100 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +% +%my $orderby = 'ORDER BY svcnum'; +%my %svc_domain = (); +%my @extra_sql = (); +%if ( $query eq 'svcnum' ) { +% #$orderby = 'ORDER BY svcnum'; +%} elsif ( $query eq 'domain' ) { +% $orderby = 'ORDER BY domain'; +%} elsif ( $query eq 'UN_svcnum' ) { #UN searches need to be acl'ed (and need to +% #fix $agentnums_sql +% #$orderby = 'ORDER BY svcnum'; +% push @extra_sql, 'pkgnum IS NULL'; +%} elsif ( $query eq 'UN_domain' ) { #UN searches need to be acl'ed (and need to +% #fix $agentnums_sql +% $orderby = 'ORDER BY domain'; +% push @extra_sql, 'pkgnum IS NULL'; +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% #$orderby = 'ORDER BY svcnum'; +% push @extra_sql, "svcpart = $1"; +%} else { +% $cgi->param('domain') =~ /^([\w\-\.]+)$/; +% $svc_domain{'domain'} = $1; +%} +% +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +%#here is the agent virtualization +%push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = ''; +%if ( @extra_sql ) { +% $extra_sql = ( keys(%svc_domain) ? ' AND ' : ' WHERE ' ). +% join(' AND ', @extra_sql ); +%} +% +%my $count_query = "SELECT COUNT(*) FROM svc_domain $addl_from "; +%if ( keys %svc_domain ) { +% $count_query .= ' WHERE '. +% join(' AND ', map "$_ = ". dbh->quote($svc_domain{$_}), +% keys %svc_domain +% ); +%} +%$count_query .= $extra_sql; +% +%my $sql_query = { +% 'table' => 'svc_domain', +% 'hashref' => \%svc_domain, +% 'select' => join(', ', +% 'svc_domain.*', +% 'part_svc.svc', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => "$extra_sql $orderby", +% 'addl_from' => $addl_from, +%}; +% +%my $link = [ "${p}view/svc_domain.cgi?", 'svcnum' ]; +% +%#smaller false laziness w/svc_*.cgi here +%my $link_cust = sub { +% my $svc_x = shift; +% $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +%}; +% +% +<% include( 'elements/search.html', + 'title' => "Domain Search Results", + 'name' => 'domains', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Domain', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'domain', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi new file mode 100755 index 000000000..e85d6d7b3 --- /dev/null +++ b/httemplate/search/svc_external.cgi @@ -0,0 +1,105 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +%my(@svc_external,$sortby); +%if ( $query eq 'svcnum' ) { +% $sortby=\*svcnum_sort; +% @svc_external=qsearch('svc_external',{}); +%} elsif ( $query eq 'id' ) { +% $sortby=\*id_sort; +% @svc_external=qsearch('svc_external',{}); +%} elsif ( $query eq 'UN_svcnum' ) { +% $sortby=\*svcnum_sort; +% @svc_external = grep qsearchs('cust_svc',{ +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% }), qsearch('svc_external',{}); +%} elsif ( $query eq 'UN_id' ) { +% $sortby=\*id_sort; +% @svc_external = grep qsearchs('cust_svc',{ +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% }), qsearch('svc_external',{}); +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% @svc_external = +% qsearch( 'svc_external', {}, '', +% " WHERE $1 = ( SELECT svcpart FROM cust_svc ". +% " WHERE cust_svc.svcnum = svc_external.svcnum ) " +% ); +% $sortby=\*svcnum_sort; +%} else { +% $cgi->param('id') =~ /^([\w\-\.]+)$/; +% my($id)=$1; +% #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain}); +% @svc_external = qsearchs('svc_external',{'id'=>$id}); +%} +% +%if ( scalar(@svc_external) == 1 ) { +% +% +<% $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %> +% +% +%} elsif ( scalar(@svc_external) == 0 ) { +% +% +<% include('/elements/header.html', 'External Search Results' ) %> + + No matching external services found +% } else { +% +% +<% include('/elements/header.html', 'External Search Results', '') %> + + <% scalar(@svc_external) %> matching external services found + <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> + <TR> + <TH>Service #</TH> + <TH><% FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TH> + <TH><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH> + </TR> +% +% foreach my $svc_external ( +% sort $sortby (@svc_external) +% ) { +% my($svcnum, $id, $title)=( +% $svc_external->svcnum, +% $svc_external->id, +% $svc_external->title, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub id_sort { +% $a->getfield('id') <=> $b->getfield('id'); +%} +% +% + diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi new file mode 100755 index 000000000..dc002d96a --- /dev/null +++ b/httemplate/search/svc_forward.cgi @@ -0,0 +1,128 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +% +%my $orderby; +% +%my @extra_sql = (); +%if ( $query =~ /^UN_(.*)$/ ) { #UN searches need to be acl'ed (and need to +% #fix $agentnums_sql +% $query = $1; +% push @extra_sql, 'pkgnum IS NULL'; +%} +% +%if ( $query eq 'svcnum' ) { +% $orderby = 'ORDER BY svcnum'; +%} else { +% eidiot('unimplemented'); +%} +% +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +%#here is the agent virtualization +%push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = +% scalar(@extra_sql) +% ? ' WHERE '. join(' AND ', @extra_sql ) +% : ''; +% +%my $count_query = "SELECT COUNT(*) FROM svc_forward $addl_from $extra_sql"; +%my $sql_query = { +% 'table' => 'svc_forward', +% 'hashref' => {}, +% 'select' => join(', ', +% 'svc_forward.*', +% 'part_svc.svc', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => "$extra_sql $orderby", +% 'addl_from' => $addl_from, +%}; +% +%# <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> +% +%my $link = [ "${p}view/svc_forward.cgi?", 'svcnum' ]; +% +%my $format_src = sub { +% my $svc_forward = shift; +% if ( $svc_forward->srcsvc_acct ) { +% $svc_forward->srcsvc_acct->email; +% } else { +% my $src = $svc_forward->src; +% $src = "<I>(anything)</I>$src" if $src =~ /^@/; +% $src; +% } +%}; +% +%my $link_src = sub { +% my $svc_forward = shift; +% if ( $svc_forward->srcsvc_acct ) { +% [ "${p}view/svc_acct.cgi?", 'srcsvc' ]; +% } else { +% ''; +% } +%}; +% +%my $format_dst = sub { +% my $svc_forward = shift; +% if ( $svc_forward->dstsvc_acct ) { +% $svc_forward->dstsvc_acct->email; +% } else { +% $svc_forward->dst; +% } +%}; +% +%my $link_dst = sub { +% my $svc_forward = shift; +% if ( $svc_forward->dstsvc_acct ) { +% [ "${p}view/svc_acct.cgi?", 'dstsvc' ]; +% } else { +% ''; +% } +%}; +% +%#smaller false laziness w/svc_*.cgi here +%my $link_cust = sub { +% my $svc_x = shift; +% $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +%}; +% +% +<% include( 'elements/search.html', + 'title' => "Mail forward Search Results", + 'name' => 'mail forwards', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Mail to', + 'Forwards to', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + $format_src, + $format_dst, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link_src, + $link_dst, + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi new file mode 100644 index 000000000..26e2090e6 --- /dev/null +++ b/httemplate/search/svc_phone.cgi @@ -0,0 +1,95 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +% +%my $orderby = 'ORDER BY svcnum'; +%my %svc_phone = (); +%my @extra_sql = (); +%if ( $query eq 'svcnum' ) { +% #$orderby = 'ORDER BY svcnum'; +%} elsif ( $query eq 'phonenum' ) { +% $orderby = 'ORDER BY phonenum'; +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% #$orderby = 'ORDER BY svcnum'; +% push @extra_sql, "svcpart = $1"; +%} else { +% $cgi->param('phonenum') =~ /^([\d\- ]+)$/; +% ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g; +%} +% +%my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. +% ' LEFT JOIN part_svc USING ( svcpart ) '. +% ' LEFT JOIN cust_pkg USING ( pkgnum ) '. +% ' LEFT JOIN cust_main USING ( custnum ) '; +% +%#here is the agent virtualization +%push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; +% +%my $extra_sql = ''; +%if ( @extra_sql ) { +% $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ). +% join(' AND ', @extra_sql ); +%} +% +%my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from "; +%if ( keys %svc_phone ) { +% $count_query .= ' WHERE '. +% join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}), +% keys %svc_phone +% ); +%} +%$count_query .= $extra_sql; +% +%my $sql_query = { +% 'table' => 'svc_phone', +% 'hashref' => \%svc_phone, +% 'select' => join(', ', +% 'svc_phone.*', +% 'part_svc.svc', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => "$extra_sql $orderby", +% 'addl_from' => $addl_from, +%}; +% +%my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ]; +% +%#smaller false laziness w/svc_*.cgi here +%my $link_cust = sub { +% my $svc_x = shift; +% $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +%}; +% +% +<% include( 'elements/search.html', + 'title' => "Phone number search results", + 'name' => 'phone numbers', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Country code', + 'Phone number', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'countrycode', + 'phonenum', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + $link, + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi new file mode 100755 index 000000000..b0f1d5c80 --- /dev/null +++ b/httemplate/search/svc_www.cgi @@ -0,0 +1,74 @@ +% +% +%#my $conf = new FS::Conf; +% +%my($query)=$cgi->keywords; +%$query ||= ''; #to avoid use of unitialized value errors +%my $orderby; +%if ( $query eq 'svcnum' ) { +% $orderby = 'ORDER BY svcnum'; +%} else { +% eidiot('unimplemented'); +%} +% +%my $count_query = 'SELECT COUNT(*) FROM svc_www'; +%my $sql_query = { +% 'table' => 'svc_www', +% 'hashref' => {}, +% 'select' => join(', ', +% 'svc_www.*', +% 'part_svc.svc', +% 'cust_main.custnum', +% FS::UI::Web::cust_sql_fields(), +% ), +% 'extra_sql' => $orderby, +% 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )'. +% 'LEFT JOIN cust_pkg USING ( pkgnum )'. +% 'LEFT JOIN cust_main USING ( custnum )', +%}; +% +%my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +%#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +%my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ]; +% +%#smaller false laziness w/svc_*.cgi here +%my $link_cust = sub { +% my $svc_x = shift; +% $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +%}; +% +% +<% include( 'elements/search.html', + 'title' => 'Virtual Host Search Results', + 'name' => 'virtual hosts', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Zone', + 'User', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + sub { $_[0]->domain_record->zone }, + sub { + my $svc_www = shift; + my $svc_acct = $svc_www->svc_acct; + $svc_acct + ? $svc_acct->email + : ''; + }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + '', + $ulink, + ( map { $link_cust } + FS::UI::Web::cust_header() + ), + ], + ) +%> diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi new file mode 100755 index 000000000..fd6a81a75 --- /dev/null +++ b/httemplate/view/cust_bill-logo.cgi @@ -0,0 +1,16 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query) = $cgi->keywords; +%$query =~ /^([^\.\/]*)$/; +%my $templatename = $1; +%if ( $templatename && $conf->exists("logo_$templatename.png") ) { +% $templatename = "_$templatename"; +%} else { +% $templatename = ''; +%} +% +%http_header('Content-Type' => 'image/png' ); +% +<% $conf->config_binary("logo$templatename.png") %> diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi new file mode 100755 index 000000000..06bb965eb --- /dev/null +++ b/httemplate/view/cust_bill-pdf.cgi @@ -0,0 +1,18 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)(.pdf)?$/; +%my $templatename = $2; +%my $invnum = $3; +% +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Invoice #$invnum not found!" unless $cust_bill; +% +%my $pdf = $cust_bill->print_pdf( '', $templatename); +% +%http_header('Content-Type' => 'application/pdf' ); +%http_header('Content-Length' => length($pdf) ); +%http_header('Cache-control' => 'max-age=60' ); +% +<% $pdf %> diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi new file mode 100755 index 000000000..f838e1b17 --- /dev/null +++ b/httemplate/view/cust_bill-ps.cgi @@ -0,0 +1,14 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $templatename = $2; +%my $invnum = $3; +% +%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( '', $templatename) %> diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi new file mode 100755 index 000000000..3772e8dd0 --- /dev/null +++ b/httemplate/view/cust_bill.cgi @@ -0,0 +1,155 @@ +% +% +%#untaint invnum +%my($query) = $cgi->keywords; +%$query =~ /^((.+)-)?(\d+)$/; +%my $templatename = $2; +%my $invnum = $3; +% +%my $conf = new FS::Conf; +% +%my @payby = grep /\w/, $conf->config('payby'); +%#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +%@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) +% unless @payby; +%my %payby = map { $_=>1 } @payby; +% +%my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); +%die "Invoice #$invnum not found!" unless $cust_bill; +%my $custnum = $cust_bill->getfield('custnum'); +% +%#my $printed = $cust_bill->printed; +% +%my $link = $templatename ? "$templatename-$invnum" : $invnum; +% +% + +<% include("/elements/header.html",'Invoice View', menubar( + "Main Menu" => $p, + "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +)) %> +% if ( $cust_bill->owed > 0 +% && ( $payby{'BILL'} || $payby{'CASH'} || $payby{'WEST'} || $payby{'MCRD'} ) +% ) +% { +% my $s = 0; +% + + + Post +% if ( $payby{'BILL'} ) { + + + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=BILL;invnum=<% $invnum %>">check</A> +% } +% if ( $payby{'CASH'} ) { + + + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=CASH;invnum=<% $invnum %>">cash</A> +% } +% if ( $payby{'WEST'} ) { + + + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;invnum=<% $invnum %>">Western Union</A> +% } +% if ( $payby{'MCRD'} ) { + + + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;invnum=<% $invnum %>">manual credit card</A> +% } + + + payment against this invoice<BR> +% } + + +<A HREF="<% $p %>misc/print-invoice.cgi?<% $link %>">Re-print this invoice</A> +% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { + + | <A HREF="<% $p %>misc/email-invoice.cgi?<% $link %>">Re-email + this invoice</A> +% } +% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { + + | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $link %>">Re-fax + this invoice</A> +% } + + +<BR><BR> +% if ( $conf->exists('invoice_latex') ) { + + <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>.pdf">View typeset invoice</A> + <BR><BR> +% } +% #false laziness with search/cust_bill_event.cgi +% unless ( $templatename ) { + + + <% table() %> + <TR> + <TH>Event</TH> + <TH>Date</TH> + <TH>Status</TH> + </TR> +% foreach my $cust_bill_event ( +% sort { $a->_date <=> $b->_date } $cust_bill->cust_bill_event +% ) { +% +% my $status = $cust_bill_event->status; +% $status .= ': '. encode_entities($cust_bill_event->statustext) +% if $cust_bill_event->statustext; +% my $part_bill_event = $cust_bill_event->part_bill_event; +% + + <TR> + <TD><% $part_bill_event->event %> +% if ( $part_bill_event->templatename ) { +% my $alt_templatename = $part_bill_event->templatename; +% my $alt_link = "$alt_templatename-$invnum"; +% + + ( <A HREF="<% $p %>view/cust_bill.cgi?<% $alt_link %>">view</A> + | <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $alt_link %>.pdf">view + typeset</A> + | <A HREF="<% $p %>misc/print-invoice.cgi?<% $alt_link %>">re-print</A> +% if ( grep { $_ ne 'POST' } +% $cust_bill->cust_main->invoicing_list ) { + + | <A HREF="<% $p %>misc/email-invoice.cgi?<% $alt_link %>">re-email</A> +% } +% if ( $conf->exists('hylafax') +% && length($cust_bill->cust_main->fax) ) { + + | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $alt_link %>">re-fax</A> +% } + + + ) +% } + + + </TD> + <TD><% time2str("%a %b %e %T %Y", $cust_bill_event->_date) %></TD> + <TD><% $status %></TD> + </TR> +% } + + + </TABLE> + <BR> +% } +% if ( $conf->exists('invoice_html') ) { + + <% join('', $cust_bill->print_html('', $templatename) ) %> +% } else { + + <PRE><% join('', $cust_bill->print_text('', $templatename) ) %></PRE> +% } + + +</BODY></HTML> diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi new file mode 100755 index 000000000..70d424065 --- /dev/null +++ b/httemplate/view/cust_main.cgi @@ -0,0 +1,159 @@ +% +% +%my $conf = new FS::Conf; +% +%my $curuser = $FS::CurrentUser::CurrentUser; +% +%die "No customer specified (bad URL)!" unless $cgi->keywords; +%my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array +%$query =~ /^(\d+)$/; +%my $custnum = $1; +%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +%die "Customer not found!" unless $cust_main; +% +% + + +<% include("/elements/header.html","Customer View: ". $cust_main->name ) %> +% if ( $curuser->access_right('Edit customer') ) { + + <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> | +% } + + + +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT> +<SCRIPT TYPE="text/javascript" SRC="../elements/iframecontentmws.js"></SCRIPT> + +<SCRIPT TYPE="text/javascript"> +function areyousure(href, message) { + if (confirm(message) == true) + window.location.href = href; +} +</SCRIPT> + +<SCRIPT TYPE="text/javascript"> +% +%my $ban = ''; +%if ( $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ ) { +% $ban = '<BR><P ALIGN="center">'. +% '<INPUT TYPE="checkbox" NAME="ban" VALUE="1"> Ban this customer\\\'s '; +% if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) { +% $ban .= 'credit card'; +% } elsif ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) { +% $ban .= 'ACH account'; +% } +%} +% + + +var confirm_cancel = '<FORM METHOD="POST" ACTION="<% $p %>misc/cust_main-cancel.cgi"> <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> <BR><P ALIGN="center"><B>Permanently delete all services and cancel this customer?</B> <% $ban%><BR><P ALIGN="CENTER"> <INPUT TYPE="submit" VALUE="Cancel customer"> <INPUT TYPE="BUTTON" VALUE="Don\'t cancel" onClick="cClick()"> </FORM> '; + +</SCRIPT> +% if ( $curuser->access_right('Cancel customer') +% && $cust_main->ncancelled_pkgs +% ) { +% + + <A HREF="javascript:void(0);" onClick="overlib(confirm_cancel, CAPTION, 'Confirm cancellation', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' ); return false; ">Cancel this customer</A> | +% } +% if ( $conf->exists('deletecustomers') +% && $curuser->access_right('Delete customer') +% ) { +% + + <A HREF="<% $p %>misc/delete-customer.cgi?<% $custnum%>">Delete this customer</A> | +% } +% unless ( $conf->exists('disable_customer_referrals') ) { + + <A HREF="<% popurl(2) %>edit/cust_main.cgi?referral_custnum=<% $custnum %>">Refer a new customer</A> | + <A HREF="<% popurl(2) %>search/cust_main.cgi?referral_custnum=<% $custnum %>">View this customer's referrals</A> +% } + + + +<BR><BR> +% +%my $signupurl = $conf->config('signupurl'); +%if ( $signupurl ) { +% + + This customer's signup URL: <A HREF="<% $signupurl %>?ref=<% $custnum %>"><% $signupurl %>?ref=<% $custnum %></A><BR><BR> +% } + + +<A NAME="cust_main"></A> +<TABLE BORDER=0> +<TR> + <TD VALIGN="top"> + <% include('cust_main/contacts.html', $cust_main ) %> + </TD> + <TD VALIGN="top" STYLE="padding-left: 54px"> + <% include('cust_main/misc.html', $cust_main ) %> +% if ( $conf->config('payby-default') ne 'HIDE' ) { + + <BR> + <% include('cust_main/billing.html', $cust_main ) %> +% } + + </TD> +</TR> +</TABLE> +% +%if ( $cust_main->comments =~ /[^\s\n\r]/ ) { +% + +<BR> +Comments +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +<TR> + <TD BGCOLOR="#ffffff"> + <PRE><% encode_entities($cust_main->comments) %></PRE> + </TD> +</TR> +</TABLE></TABLE> +% } +<BR><BR> +% my $notecount = scalar($cust_main->notes()); +% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) { + +<A NAME="cust_main_note"><FONT SIZE="+2">Notes</FONT></A><BR> +% if ( $curuser->access_right('Add customer note') && +% ! $conf->exists('cust_main-disable_notes') +% ) { + + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_main_note.cgi?custnum=<% $cust_main->custnum %>', 616, 386, 'cust_main_note_popup' ), CAPTION, 'Enter customer note', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;">Add customer note</A> + +% } + +<BR> + +% if ($notecount) { + +<iframe src="<% $p %>view/cust_main/notes.html?custnum=<% $cust_main->custnum %>" height="186" width="616" name="cust_main_notes" frameborder="0" marginborder="0" marginheight="0" scrolling="auto"> + <div><br>[iframe not supported]<br><br></div> +</iframe> + +% } + +% } + + +% if ( $conf->config('ticket_system') ) { + + <BR><BR> + <% include('cust_main/tickets.html', $cust_main ) %> +% } + + +<BR><BR> +<% include('cust_main/packages.html', $cust_main ) %> +% if ( $conf->config('payby-default') ne 'HIDE' ) { + + <% include('cust_main/payment_history.html', $cust_main ) %> +% } + + +<% include('/elements/footer.html') %> diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html new file mode 100644 index 000000000..d1be8936f --- /dev/null +++ b/httemplate/view/cust_main/billing.html @@ -0,0 +1,188 @@ +% +% my( $cust_main ) = @_; +% my @invoicing_list = $cust_main->invoicing_list; +% my $conf = new FS::Conf; +% my $money_char = $conf->config('money_char') || '$'; +% + + +Billing information +(<A HREF="<% $p %>misc/bill.cgi?<% $cust_main->custnum %>">Bill now</A>) +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +% +%( my $balance = $cust_main->balance ) +% =~ s/^(\-?)(.*)$/<FONT SIZE=+1>$1<\/FONT>$money_char$2/; +% + + +<TR> + <TD ALIGN="right">Balance due</TD> + <TD BGCOLOR="#ffffff"><B><% $balance %></B></TD> +</TR> + +<TR> + <TD ALIGN="right">Billing type</TD> + <TD BGCOLOR="#ffffff"> +% if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) { + + + Credit card <% $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' %> + </TD> +</TR> +<TR> + <TD ALIGN="right">Card number</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payinfo_masked %></TD> +</TR> +% +%#false laziness w/elements/select-month_year.html & edit/cust_main/billing.html +%my( $mon, $year ); +%my $date = $cust_main->paydate || '12-2037'; +%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +%} else { +% warn "unrecognized expiration date format: $date"; +% ( $mon, $year ) = ( '', '' ); +%} +% + +<TR> + <TD ALIGN="right">Expiration</TD> + <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD> +</TR> +% if ( $cust_main->paystart_month ) { + + <TR> + <TD ALIGN="right">Start date</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->paystart_month. '/'. $cust_main->paystart_year %> + </TR> +% } elsif ( $cust_main->payissue ) { + + <TR> + <TD ALIGN="right">Issue #</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payissue %> + </TR> +% } + + +<TR> + <TD ALIGN="right">Name on card</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> +</TR> +% } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') { +% my( $account, $aba ) = split('@', $cust_main->payinfo ); +% + + + Electronic check <% $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' %> + </TD> +</TR> +<TR> + <TD ALIGN="right">ABA/Routing code</TD> + <TD BGCOLOR="#ffffff"><% $aba %></TD> +</TR> +<TR> + <TD ALIGN="right">Account number</TD> + <TD BGCOLOR="#ffffff"><% 'x'x(length($account)-2). substr($account,(length($account)-2)) %></TD> +</TR> +<TR> + <TD ALIGN="right">Bank name</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> +</TR> +% } elsif ( $cust_main->payby eq 'LECB' ) { +% $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/; +% my $payinfo = "$1-$2-$3"; +% + + + Phone bill billing + </TD> +</TR> +<TR> + <TD ALIGN="right">Phone number</TD> + <TD BGCOLOR="#ffffff"><% $payinfo %></TD> +</TR> +% } elsif ( $cust_main->payby eq 'BILL' ) { + + + Billing + </TD> +</TR> +% if ( $cust_main->payinfo ) { + +<TR> + <TD ALIGN="right">P.O. </TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD> +</TR> +% } + + +<TR> + <TD ALIGN="right">Attention</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payname %></TD> +</TR> +% } elsif ( $cust_main->payby eq 'COMP' ) { + + + Complimentary + </TD> +</TR> +<TR> + <TD ALIGN="right">Authorized by</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD> +</TR> +% +%#false laziness w/above etc. +%my( $mon, $year ); +%my $date = $cust_main->paydate || '12-2037'; +%if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format +% ( $mon, $year ) = ( $2, $1 ); +%} elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) { +% ( $mon, $year ) = ( $1, $3 ); +%} else { +% warn "unrecognized expiration date format: $date"; +% ( $mon, $year ) = ( '', '' ); +%} +% + +<TR> + <TD ALIGN="right">Expiration</TD> + <TD BGCOLOR="#ffffff"><% "$mon/$year" %></TD> +</TR> +% } + + +<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"> + <% ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no' %> + </TD> +</TR> +<TR> + <TD ALIGN="right">FAX invoices</TD> + <TD BGCOLOR="#ffffff"> + <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? 'yes' : 'no' %> + </TD> +</TR> +<TR> + <TD ALIGN="right">Email invoices</TD> + <TD BGCOLOR="#ffffff"> + <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %> + </TD> +</TR> +% if ( $conf->exists('voip-cust_cdr_spools') ) { + + <TR> + <TD ALIGN="right">Spool CDRs</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->spool_cdr ? 'yes' : 'no' %></TD> + </TR> +% } + + +</TABLE></TD></TR></TABLE> + diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html new file mode 100644 index 000000000..9c2b38f9a --- /dev/null +++ b/httemplate/view/cust_main/contacts.html @@ -0,0 +1,155 @@ +% +% my( $cust_main ) = @_; +% my $conf = new FS::Conf; +% + + +Billing address +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +<TR> + <TD ALIGN="right">Contact name</TD> + <TD COLSPAN=3 BGCOLOR="#ffffff"> + <% $cust_main->last. ', '. $cust_main->first %> + </TD> +% if ( $conf->exists('show_ss') ) { + + <TD ALIGN="right">SS#</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->ss || ' ' %></TD> +% } + +</TR> +<TR> + <TD ALIGN="right">Company</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->company %></TD> +</TR> +<TR> + <TD ALIGN="right">Address</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->address1 %></TD> +</TR> +% if ( $cust_main->address2 ) { + +<TR> + <TD ALIGN="right"> </TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->address2 %></TD> +</TR> +% } + +<TR> + <TD ALIGN="right">City</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->city %></TD> + <TD ALIGN="right">State</TD> + <TD BGCOLOR="#ffffff"><% state_label($cust_main->state, $cust_main->country) %></TD> + <TD ALIGN="right">Zip</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->zip %></TD> +</TR> +<TR> + <TD ALIGN="right">Country</TD> + <TD BGCOLOR="#ffffff"><% code2country($cust_main->country) %></TD> +</TR> +% +% my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/ +% ? 'Day Phone' +% : FS::Msgcat::_gettext('daytime'); +% my $night_label = FS::Msgcat::_gettext('night') =~ /^(night)?$/ +% ? 'Night Phone' +% : FS::Msgcat::_gettext('night'); +% + +<TR> + <TD ALIGN="right"><% $daytime_label %></TD> + <TD COLSPAN=6 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->daytime, + 'callable'=>1 + ) + %> + </TD> +</TR> +<TR> + <TD ALIGN="right"><% $night_label %></TD> + <TD COLSPAN=6 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->night, + 'callable'=>1 + ) + %> + </TD> +</TR> +<TR> + <TD ALIGN="right">Fax</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"> + <% $cust_main->fax || ' ' %> + </TD> +</TR> +</TABLE></TD></TR></TABLE> +% if ( defined $cust_main->dbdef_table->column('ship_last') ) { +% my $pre = $cust_main->ship_last ? 'ship_' : ''; +% + + +<BR> +Service address +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +<TR> + <TD ALIGN="right">Contact name</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"> + <% $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") %> + </TD> +</TR> +<TR> + <TD ALIGN="right">Company</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") %></TD> +</TR> +<TR> + <TD ALIGN="right">Address</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") %></TD> +</TR> +% if ( $cust_main->get("${pre}address2") ) { + +<TR> + <TD ALIGN="right"> </TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") %></TD> +</TR> +% } + +<TR> + <TD ALIGN="right">City</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") %></TD> + <TD ALIGN="right">State</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}state") %></TD> + <TD ALIGN="right">Zip</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}zip") %></TD> +</TR> +<TR> + <TD ALIGN="right">Country</TD> + <TD BGCOLOR="#ffffff"><% code2country( $cust_main->get("${pre}country") ) %></TD> +</TR> +<TR> + <TD ALIGN="right"><% $daytime_label %></TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->get("${pre}daytime"), + 'callable'=>1 + ) + %> + </TD> +</TR> +<TR> + <TD ALIGN="right"><% $night_label %></TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"> + <% include('/elements/phonenumber.html', + $cust_main->get("${pre}night"), + 'callable'=>1 + ) + %> + </TD> +</TR> +<TR> + <TD ALIGN="right">Fax</TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"> + <% $cust_main->get("${pre}fax") || ' ' %> + </TD> +</TR> +</TABLE></TD></TR></TABLE> +% } + diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html new file mode 100644 index 000000000..fdc5ba4ea --- /dev/null +++ b/httemplate/view/cust_main/misc.html @@ -0,0 +1,104 @@ +% +% my( $cust_main ) = @_; +% my $conf = new FS::Conf; +% my $date_format = ($conf->config('date_format') || "%m/%d/%Y"); +% + + +<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> + +<TR> + <TD ALIGN="right">Customer number</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->custnum %></TD> +</TR> + +<TR> + <TD ALIGN="right">Status</TD> + <TD BGCOLOR="#ffffff"><FONT COLOR="#<% $cust_main->statuscolor %>"><B><% ucfirst($cust_main->status) %></B></FONT></TD> +</TR> +% +% my @agents = qsearch( 'agent', {} ); +% my $agent; +% unless ( scalar(@agents) == 1 ) { +% $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } ); +% + + +<TR> + <TD ALIGN="right">Agent</TD> + <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD> +</TR> +% +% } else { +% $agent = $agents[0]; +% } +% +% if ( $cust_main->agent_custid ) { +% + + +<TR> + <TD ALIGN="right">Agent customer ref#</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->agent_custid %></TD> +</TR> +% +% } +% +% unless ( FS::part_referral->num_part_referral == 1 ) { +% my $referral = qsearchs('part_referral', { +% 'refnum' => $cust_main->refnum +% } ); +% + + +<TR> + <TD ALIGN="right">Advertising source</TD> + <TD BGCOLOR="#ffffff"><% $referral->refnum %>: <% $referral->referral%></TD> +</TR> +% } + + +<TR> + <TD ALIGN="right">Referring Customer</TD> + <TD BGCOLOR="#ffffff"> +% +% my $referring_cust_main = ''; +% if ( $cust_main->referral_custnum +% && ( $referring_cust_main = +% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) +% ) +% ) { +% + + +<A HREF="<% popurl(1) %>cust_main.cgi?<% $cust_main->referral_custnum %>"><%$cust_main->referral_custnum %>: +<% + ( $referring_cust_main->company + ? $referring_cust_main->company. ' ('. + $referring_cust_main->last. ', '. $referring_cust_main->first. + ')' + : $referring_cust_main->last. ', '. $referring_cust_main->first + ) +%></A> +% } + + + </TD> +</TR> + +<TR> + <TD ALIGN="right">Order taker</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->otaker %></TD> +</TR> + +% if ( $conf->exists('cust_main-enable_birthdate') ) { + + <TR> + <TD ALIGN="right">Date of Birth</TD> + <TD BGCOLOR="#ffffff"><% $cust_main->birthdate ? time2str($date_format, $cust_main->birthdate) : '' %></TD> + </TR> + +% } + +</TABLE></TD></TR></TABLE> + diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html new file mode 100755 index 000000000..c1a33b020 --- /dev/null +++ b/httemplate/view/cust_main/notes.html @@ -0,0 +1,74 @@ +% +% my $conf = new FS::Conf; +% +% $cgi->param('custnum') =~ /^(\d+)$/ +% or die "No customer specified (bad URL)!"; +% my $custnum = $1; +% +% my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} ); +% die "Custimer not found!" unless $cust_main; +% +% my (@notes) = $cust_main->notes(); +% if ( scalar(@notes) ) { + +<STYLE TYPE="text/css"> + +body { background: #e8e8e8 } +.inv table { border: none } +.inv TH { border: none } +.inv TD { border: none } + +</STYLE> + +<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 BORDER=0 > + +%#<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% +% foreach my $note (@notes) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% + +<TR> + <% note_datestr($note,$conf,$bgcolor) %> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <%$note->otaker%> + </TD> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <%$note->comments%> + </TD> +</TR> + +% } #end display notes + +</TABLE> +% } else { + +<BR> +% } +% +%#subroutines +% +%sub note_datestr { +% my($note, $conf, $bgcolor) = @_ or return ''; +% my $format=qq{<TD class="inv" bgcolor="$bgcolor" align="left"><B>%b</B></TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" align="right"><B> %o,</B></TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" align="right"><B> %Y </B></TD>}; +% $format .= qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="right"><B> %l</TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="center"><B>:</B></TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left"><B>%M</B></TD>}. +% qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left"><B> %P </B></TD>} +% if $conf->exists('cust_main_note-display_times'); +% ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g; +% $strip; +% } +% + diff --git a/httemplate/view/cust_main/order_pkg.html b/httemplate/view/cust_main/order_pkg.html new file mode 100644 index 000000000..6edd18c05 --- /dev/null +++ b/httemplate/view/cust_main/order_pkg.html @@ -0,0 +1,40 @@ +% +% my( $cust_main ) = @_; +% + + +<SCRIPT TYPE="text/javascript"> +function enable_order_pkg () { + if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) { + document.OrderPkgForm.submit.disabled = false; + } else { + document.OrderPkgForm.submit.disabled = true; + } +} +</SCRIPT> + +<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST"> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>"> + +<SELECT NAME="pkgpart" onChange="enable_order_pkg()"><OPTION>Order additional package +% +%foreach my $part_pkg ( +% qsearch( 'part_pkg', { 'disabled' => '' }, '', +% ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '. +% ' WHERE typenum = '. $cust_main->agent->typenum. +% ' AND type_pkgs.pkgpart = part_pkg.pkgpart )' +% ) +%) { +% + + + <OPTION VALUE="<% $part_pkg->pkgpart %>"><% $part_pkg->pkg %> - <% $part_pkg->comment %> +% } + + +</SELECT> + +<INPUT NAME="submit" TYPE="submit" VALUE="Order Package" disabled> + +</FORM> diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html new file mode 100755 index 000000000..0278f22e9 --- /dev/null +++ b/httemplate/view/cust_main/packages.html @@ -0,0 +1,575 @@ +% +% my( $cust_main ) = @_; +% my $conf = new FS::Conf; +% +% my $curuser = $FS::CurrentUser::CurrentUser; +% +% my $packages = get_packages($cust_main, $conf); +% + + +<A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A> +% if ( $curuser->access_right('Order customer package') ) { + + <% include('order_pkg.html', $cust_main ) %> +% } +% if ( $curuser->access_right('One-time charge') +% && $conf->config('payby-default') ne 'HIDE' +% ) { +% + + <% include('quick-charge.html', $cust_main ) %> +% } +% if ( $curuser->access_right('Bulk change customer packages') ) { + + <A HREF="<% $p %>edit/cust_pkg.cgi?<% $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services) +% } + + +<BR><BR> +% if ( @$packages ) { + +Current packages +% } +% if ( $cust_main->num_cancelled_pkgs ) { +% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledpackages') +% && ! $cgi->param('showcancelledpackages') +% ) +% ) +% { +% $cgi->param('showcancelledpackages', 1); +% + + ( <a href="<% $cgi->self_url %>">show +% } else { +% $cgi->param('showcancelledpackages', 0); +% + + ( <a href="<% $cgi->self_url %>">hide +% } + + cancelled packages</a> ) +% } +% if ( @$packages ) { + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Package</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH> +</TR> +% +%foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% + + +<!--pkgnum: <%$pkg->{pkgnum}%>--> +<TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A NAME="cust_pkg<%$pkg->{pkgnum}%>"><%$pkg->{pkgnum}%></A>: + <%$pkg->{pkg}%> - <%$pkg->{comment}%><BR> + <FONT SIZE=-1> +% unless ( $pkg->{cancel} ) { +% if ( $curuser->access_right('Change customer package') ) { + + ( <%pkg_change_link($pkg)%> ) +% } +% if ( $curuser->access_right('Edit customer package dates') ) { + + ( <%pkg_dates_link($pkg)%> ) +% } +% if ( $curuser->access_right('Customize customer package') ) { + + ( <%pkg_customize_link($pkg,$cust_main->custnum)%> ) +% } +% } + + </FONT> + </TD> + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> +% +% sub myfreq { +% my $part_pkg = shift; +% my $freq = $part_pkg->freq_pretty; +% $freq =~ s/ / /g; +% $freq; +% } +% +% #this should use cust_pkg->status and cust_pkg->statuscolor eventually +% +% my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4; +% my $width = $conf->exists('cust_pkg-display_times') ? '38%' : '56%'; +% +% #false laziness w/edit/REAL_cust_pkg.cgi +% my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until ); +% unless ( $pkg->{'part_pkg'}->is_prepaid ) { +% $billed_or_prepaid = 'billed'; +% $last_bill_or_renewed = 'Last bill'; +% $next_bill_or_prepaid_until = 'Next bill'; +% } else { +% $billed_or_prepaid = 'prepaid'; +% $last_bill_or_renewed = 'Renewed'; +% $next_bill_or_prepaid_until = 'Prepaid until'; +% } +% +% +% if ( $pkg->{cancel} ) { + <!-- #status: cancelled --> + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#ff0000"><B>Cancelled </B></FONT></TD> + <% pkg_datestr($pkg,'cancel',$conf) %> + </TR> +% unless ( $pkg->{setup} ) { + + + <TR> + <TD COLSPAN=<%$colspan%>>Never billed</TD> + </TR> +% } else { + + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($pkg, 'setup',$conf) %> + </TR> +% if ( $pkg->{'last_bill'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($pkg, 'last_bill',$conf) %> + </TR> +% } +% if ( $pkg->{'susp'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Suspended </TD> + <% pkg_datestr($pkg, 'susp',$conf) %> + </TR> +% } +% } +% } else { +% if ( $pkg->{susp} ) { + <!-- #status: suspended --> + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><FONT COLOR="#FF9900"><B>Suspended</B> </FONT></TD> + <% pkg_datestr($pkg,'susp',$conf) %> + </TR> +% unless ( $pkg->{setup} ) { + + + <TR> + <TD COLSPAN=<%$colspan%>>Never billed</TD> + </TR> +% } else { + + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($pkg, 'setup',$conf) %> + </TR> +% } +% if ( $pkg->{'last_bill'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($pkg, 'last_bill',$conf) %> + </TR> +% } + + + <!-- # next bill ?? --> +% if ( $pkg->{'expire'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> + <% pkg_datestr($pkg, 'expire',$conf) %> + </TR> +% } + + + <TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Unsuspend customer package') ) { + + ( <% pkg_unsuspend_link($pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package') ) { + + ( <% pkg_cancel_link($pkg) %> ) +% } + + </FONT> + </TD> + </TR> +% } else { + <!-- #status: active --> +% unless ( $pkg->{setup} ) { + <!-- #not setup --> +% unless ( $pkg->{'freq'} ) { + + + <TR> + <TD COLSPAN=<%$colspan%>>Not yet billed (one-time charge)</TD> + </TR> + + <TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Cancel customer package immediately') ) { + + ( <% pkg_cancel_link($pkg) %> ) +% } + + </FONT> + </TD> + </TR> +% } else { + + + <TR> + <TD COLSPAN=<%$colspan%>>Not yet billed (<% $billed_or_prepaid %> <% myfreq($pkg->{part_pkg}) %>)</TD> + </TR> +% } +% } else { + <!-- #setup --> +% unless ( $pkg->{freq} ) { + + + <TR> + <TD COLSPAN=<%$colspan%>>One-time charge</TD> + </TR> + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Billed </TD> + <% pkg_datestr($pkg,'setup',$conf) %> + </TR> +% } else { + + + <TR> + <TD COLSPAN=<%$colspan%>><FONT COLOR="#00CC00"><B>Active</B></FONT>, <% $billed_or_prepaid %> <% myfreq($pkg->{part_pkg}) %></TD> + </TR> + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Setup </TD> + <% pkg_datestr($pkg, 'setup',$conf) %> + </TR> +% } +% } +% if ( $pkg->{'last_bill'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $last_bill_or_renewed %> </TD> + <% pkg_datestr($pkg, 'last_bill',$conf) %> + </TR> +% } +% if ( $pkg->{'next_bill'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right"><% $next_bill_or_prepaid_until %> </TD> + <% pkg_datestr($pkg, 'next_bill',$conf) %> + </TR> +% } +% if ( $pkg->{'expire'} ) { + + <TR> + <TD WIDTH="<%$width%>" ALIGN="right">Expires </TD> + <% pkg_datestr($pkg, 'expire',$conf) %> + </TR> +% } +% if ( $pkg->{freq} ) { + + <TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Suspend customer package') ) { + + ( <% pkg_suspend_link($pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package immediately') ) { + + ( <% pkg_cancel_link($pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package later') ) { + + ( <% pkg_expire_link($pkg) %> ) +% } + + <FONT> + </TD> + </TR> +% } +% } +% } + + +</TABLE> +</TD> + +<TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> +% +% foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { +% foreach my $service (@{$svcpart->{services}}) { +% + + <TR> + <TD ALIGN="right" VALIGN="top" ROWSPAN=2><%svc_link($svcpart,$service)%></TD> + <TD STYLE="padding-bottom:0px"><B><%svc_label_link($svcpart,$service)%></B></TD> + </TR> +% if ( $curuser->access_right('Unprovision customer service') ) { + + <TR> + <TD ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px;padding-top:0px"><FONT SIZE="-2">( <%svc_unprovision_link($service)%> )</FONT></TD> + </TR> +% } +% } +% if ( $curuser->access_right('Provision customer service') +% && $svcpart->{count} < $svcpart->{quantity} +% ) +% { +% + + + <TR> + <TD COLSPAN=2 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px"> + <B><% svc_provision_link($pkg, $svcpart, $conf, $curuser) %></B> + </TD> + </TR> +% } +% } + + +</TABLE> +</TD> +% } #end display packages +% + + +</TABLE> +% } else { + +<BR> +% } +% +%#subroutines +% +%sub get_packages { +% my $cust_main = shift or return undef; +% my $conf = shift; +% +% my @packages = (); +% my $method; +% if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledpackages') +% && ! $cgi->param('showcancelledpackages') ) +% ) +% { +% $method = 'ncancelled_pkgs'; +% } else { +% $method = 'all_pkgs'; +% } +% +% foreach my $cust_pkg ( $cust_main->$method() ) { +% +% my $part_pkg = $cust_pkg->part_pkg; +% +% my %pkg = (); +% +% #to get back to the original object... should use it in the first place!! +% $pkg{cust_pkg} = $cust_pkg; +% $pkg{part_pkg} = $part_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 = map { +% $_->svcpart => { +% $_->part_svc->hash, +% 'quantity' => $_->quantity, +% 'count' => $cust_pkg->num_cust_svc($_->svcpart), +% #'services' => [], +% }; +% } $part_pkg->pkg_svc; +% +% foreach my $cust_svc ( $cust_pkg->cust_svc ) { +% #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} ||= { +% $cust_svc->part_svc->hash, +% 'quantity' => 0, +% 'count' => $cust_pkg->num_cust_svc($cust_svc->svcpart), +% #'services' => [], +% } ); +% +% push @{$svcpart->{services}}, $svc; +% +% } +% +% $pkg{svcparts} = [ values %svcparts ]; +% +% push @packages, \%pkg; +% +% } +% +% 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>!; +% +%} +% +%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>!; +% +%} +% +%sub svc_provision_link { +% my ($pkg, $svcpart, $conf, $curuser) = @_; +% ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/ /g; +% my $num_left = $svcpart->{quantity} - $svcpart->{count}; +% my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}"; +% +% my $url; +% if ( $svcpart->{svcdb} eq 'svc_external' +% && $conf->exists('svc_external-skip_manual') +% ) { +% $url = "${p}edit/process/$svcpart->{svcdb}.cgi?". +% "pkgnum=$pkg->{pkgnum}&". +% "svcpart=$svcpart->{svcpart}"; +% } else { +% $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart"; +% } +% +% my $link = qq!<A CLASS="provision" HREF="$url">!. +% "Provision $svc_nbsp ($num_left)</A>"; +% if ( $conf->exists('legacy_link') +% && $curuser->access_right('View/link unlinked services') +% ) +% { +% $link .= '<BR>'. +% qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!. +% qq!$pkgnum_svcpart">!. +% "Link to legacy $svc_nbsp ($num_left)</A>"; +% } +% $link; +%} +% +%sub svc_unprovision_link { +% my $svc = shift or return ''; +% qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!. +% qq!'Permanently unprovision and delete this service?')">Unprovision</A>!; +%} +% +%# This should be generalized to use config options to determine order. +%sub pkgsort_pkgnum_cancel { +% if ($a->{cancel} and $b->{cancel}) { +% return ($a->{pkgnum} <=> $b->{pkgnum}); +% } elsif ($a->{cancel} or $b->{cancel}) { +% return (-1) if ($b->{cancel}); +% return (1) if ($a->{cancel}); +% return (0); +% } else { +% return($a->{pkgnum} <=> $b->{pkgnum}); +% } +%} +% +%sub pkg_datestr { +% my($pkg, $field, $conf) = @_ or return ''; +% return ' ' unless $pkg->{$field}; +% my $format = '<TD align="left"><B>%b</B></TD>'. +% '<TD align="right"><B> %o,</B></TD>'. +% '<TD align="right"><B> %Y</B></TD>'; +% #$format .= ' <FONT SIZE=-3>%l:%M:%S%P %z</FONT>' +% $format .= '<TD ALIGN="right"><B> %l</TD>'. +% '<TD ALIGN="center"><B>:</B></TD>'. +% '<TD ALIGN="left"><B>%M</B></TD>'. +% '<TD ALIGN="left"><B> %P</B></TD>' +% if $conf->exists('cust_pkg-display_times'); +% ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g; +% $strip; +%} +% +%sub pkg_change_link { +% my $pkg = shift or return ''; +% return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!. +% qq!Change package</a>!; +%} +% +%sub pkg_suspend_link { +% my $pkg = shift or return ''; +% return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!; +%} +% +%sub pkg_unsuspend_link { +% my $pkg = shift or return ''; +% return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!; +%} +% +%sub pkg_cancel_link { +% my $pkg = shift or return ''; +% qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !. +% qq!'Permanently delete included services and cancel this package?')">!. +% qq!Cancel now</A>!; +%} +% +%sub pkg_expire_link { +% my $pkg = shift or return ''; +% qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!; +%} +% +%sub pkg_dates_link { +% my $pkg = shift or return ''; +% qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit dates</A>!; +%} +% +%sub pkg_customize_link { +% my $pkg = shift or return ''; +% my $custnum = shift; +% qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!. +% qq!pkgnum=$pkg->{pkgnum}">Customize</A>!; +%} +% +% + diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html new file mode 100644 index 000000000..4e4cd154f --- /dev/null +++ b/httemplate/view/cust_main/payment_history.html @@ -0,0 +1,561 @@ +<BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR> + +% my $s = 0; +% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=BILL;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter check payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter check payment</A> +% } + +% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=CASH;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter cash payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter cash payment</A> +% } + +% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>">Enter Western Union payment</A> +% } + +% if ( ( $payby{'CARD'} || $payby{'DCRD'} ) +% && $curuser->access_right('Process payment') +% ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>misc/payment.cgi?payby=CARD;custnum=<% $custnum %>">Process credit card payment</A> +% } + +% if ( ( $payby{'CHEK'} || $payby{'DCHK'} ) +% && $curuser->access_right('Process payment') +% ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>misc/payment.cgi?payby=CHEK;custnum=<% $custnum %>">Process electronic check (ACH) payment</A> +% } + +% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { + <% $s++ ? ' | ' : '' %> + <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>">Post manual (offline) credit card payment</A> +% } + +<BR> + +% if ( $curuser->access_right('Post credit') ) { + <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_credit.cgi?<% $custnum %>', 392, 336, 'cust_credit_popup' ), CAPTION, 'Enter credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter credit</A> + <BR> +% } + +% if ( $curuser->access_right('View customer tax exemptions') ) { + <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A> + <BR> +% } + +%#get payment history +%my @history = (); +% +%#invoices +%foreach my $cust_bill ($cust_main->cust_bill) { +% my $pre = ( $cust_bill->owed > 0 ) +% ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open ' +% : ''; +% my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : ''; +% my $invnum = $cust_bill->invnum; +% my $link = $curuser->access_right('View invoices') +% ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">! +% : ''; +% push @history, { +% 'date' => $cust_bill->_date, +% 'desc' => $link. $pre. +% "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'. +% $post. ( $link ? '</A>' : '' ), +% 'charge' => $cust_bill->charged, +% }; +%} +% +%#payments (some false laziness w/credits) +%foreach my $cust_pay ($cust_main->cust_pay) { +% +% my $payby = $cust_pay->payby; +% +% my $payinfo; +% if ( $payby eq 'CARD' ) { +% $payinfo = $cust_pay->payinfo_masked; +% } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) { +% $payinfo = "ABA $2, Acct# $1"; +% } else { +% $payinfo = $cust_pay->payinfo; +% } +% my @cust_bill_pay = $cust_pay->cust_bill_pay; +% my @cust_pay_refund = $cust_pay->cust_pay_refund; +% +% my $target = "$payby$payinfo"; +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^PREP$/Prepaid card /; +% $payby =~ s/^CARD$/Credit card #/; +% $payby =~ s/^COMP$/Complimentary by /; +% $payby =~ s/^CASH$/Cash/; +% $payby =~ s/^WEST$/Western Union/; +% $payby =~ s/^MCRD$/Manual credit card/; +% $payby =~ s/^BILL$//; +% my $info = $payby ? " ($payby$payinfo)" : ''; +% +% my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); +% if ( scalar(@cust_bill_pay) == 0 +% && scalar(@cust_pay_refund) == 0 ) { +% #completely unapplied +% $pre = '<B><FONT COLOR="#FF0000">Unapplied '; +% $post = '</FONT></B>'; +% $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!. +% $cust_pay->paynum. +% qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!; +% +% } elsif ( scalar(@cust_bill_pay) == 1 +% && scalar(@cust_pay_refund) == 0 +% && $cust_pay->unapplied == 0 ) { +% #applied to one invoice, the usual situation +% $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum; +% } elsif ( scalar(@cust_bill_pay) == 0 +% && scalar(@cust_pay_refund) == 1 +% && $cust_pay->unapplied == 0 ) { +% #applied to one refund +% $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date); +% } else { +% #complicated +% $desc = '<BR>'; +% foreach my $app ( sort { $a->_date <=> $b->_date } +% ( @cust_bill_pay, @cust_pay_refund ) ) { +% if ( $app->isa('FS::cust_bill_pay') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' applied to Invoice #'. $app->invnum. +% '<BR>'; +% #' on '. time2str("%D", $cust_bill_pay->_date). +% } elsif ( $app->isa('FS::cust_pay_refund') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' refunded on '. time2str("%D", $app->_date). +% '<BR>'; +% } else { +% die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund"; +% } +% } +% if ( $cust_pay->unapplied > 0 ) { +% $desc .= ' '. +% '<B><FONT COLOR="#FF0000">$'. +% $cust_pay->unapplied. ' unapplied</FONT></B>'. +% qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!. +% $cust_pay->paynum. +% qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!. +% '<BR>'; +% } +% } +% +% my $refund = ''; +% my $refund_days = $conf->config('card_refund-days') || 120; +% if ( $cust_pay->closed !~ /^Y/i +% && $cust_pay->payby =~ /^(CARD|CHEK)$/ +% && time-$cust_pay->_date < $refund_days*86400 +% && $cust_pay->unrefunded > 0 +% && $curuser->access_right('Refund payment') +% ) { +% $refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!. +% qq!paynum=!. $cust_pay->paynum. '"'. +% qq! TITLE="Send a refund for this payment to the payment gateway"!. +% qq!>refund</A>)!; +% } +% +% my $void = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && ( ( $cust_pay->payby eq 'CARD' +% && $curuser->access_right('Credit card void') +% ) +% || ( $cust_pay->payby eq 'CHEK' +% && $curuser->access_right('Echeck void') +% ) +% || ( $cust_pay->payby !~ /^(CARD|CHEK)$/ +% && $curuser->access_right('Regular void') +% ) +% ) +% ) +% { +% $void = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/void-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to void this payment?')"!. +% qq! TITLE="Void this payment from the database!. +% ( $cust_pay->payby =~ /^(CARD|CHEK)$/ +% ? ' (do not send anything to the payment gateway)' +% : '' +% ). '"'. +% qq!>void</A>)!; +% } +% +% my $delete = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && $conf->exists('deletepayments') +% && $curuser->access_right('Delete payment') +% ) +% { +% $delete = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/delete-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to delete this payment?')"!. +% qq! TITLE="Delete this payment from the database completely - not recommended"!. +% qq!>delete</A>)!; +% } +% +% my $unapply = ''; +% if ( $cust_pay->closed !~ /^Y/i +% && scalar(@cust_bill_pay) +% && $curuser->access_right('Unapply payment') +% ) +% { +% $unapply = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unapply-cust_pay.cgi?!. $cust_pay->paynum. +% qq!', 'Are you sure you want to unapply this payment?')"!. +% qq! TITLE="Keep this payment, but dissociate it from the invoices it is currently applied against"!. +% qq!>unapply</A>)!; +% } +% +% push @history, { +% 'date' => $cust_pay->_date, +% 'desc' => $pre. "Payment$post$info$desc". +% "$apply$refund$void$delete$unapply", +% 'payment' => $cust_pay->paid, +% 'target' => $target, +% }; +%} +% +%#voided payments +%foreach my $cust_pay_void ($cust_main->cust_pay_void) { +% +% my $payby = $cust_pay_void->payby; +% my $payinfo = $payby eq 'CARD' +% ? $cust_pay_void->payinfo_masked +% : $cust_pay_void->payinfo; +% +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^BILL$//; +% $payby =~ s/^(CARD|COMP)$/$1 /; +% my $info = $payby ? " ($payby$payinfo)" : ''; +% +% my $unvoid = ''; +% if ( $cust_pay_void->closed !~ /^Y/i +% && $curuser->access_right('Unvoid') +% ) +% { +% $unvoid = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unvoid-cust_pay_void.cgi?!. $cust_pay_void->paynum. +% qq!', 'Are you sure you want to unvoid this payment?')"!. +% qq! TITLE="Unvoid this payment from the database!. +% ( $cust_pay_void->payby =~ /^(CARD|CHEK)$/ +% ? ' (do not send anything to the payment gateway)' +% : '' +% ). '"'. +% qq!>unvoid</A>)!; +% } +% +% push @history, { +% 'date' => $cust_pay_void->_date, +% 'desc' => "<DEL>Payment $info</DEL> <I>voided ". +% time2str("%D", $cust_pay_void->void_date). +% " by ". $cust_pay_void->otaker. '</i>'. $unvoid, +% 'void_payment' => $cust_pay_void->paid, +% }; +% +%} +% +%#credits (some false laziness w/payments) +%foreach my $cust_credit ($cust_main->cust_credit) { +% +% my @cust_credit_bill = $cust_credit->cust_credit_bill; +% my @cust_credit_refund = $cust_credit->cust_credit_refund; +% +% my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' ); +% if ( scalar(@cust_credit_bill) == 0 +% && scalar(@cust_credit_refund) == 0 ) { +% #completely unapplied +% $pre = '<B><FONT COLOR="#FF0000">Unapplied '; +% $post = '</FONT></B>'; +% $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!. +% $cust_credit->crednum. +% qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!; +% } elsif ( scalar(@cust_credit_bill) == 1 +% && scalar(@cust_credit_refund) == 0 +% && $cust_credit->credited == 0 ) { +% #applied to one invoice, the usual situation +% $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum; +% } elsif ( scalar(@cust_credit_bill) == 0 +% && scalar(@cust_credit_refund) == 1 +% && $cust_credit->credited == 0 ) { +% #applied to one refund +% $desc = ' refunded on '. time2str("%D", $cust_credit_refund[0]->_date); +% } else { +% #complicated +% $desc = '<BR>'; +% foreach my $app ( sort { $a->_date <=> $b->_date } +% ( @cust_credit_bill, @cust_credit_refund ) ) { +% if ( $app->isa('FS::cust_credit_bill') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' applied to Invoice #'. $app->invnum. +% '<BR>'; +% #' on '. time2str("%D", $app->_date). +% } elsif ( $app->isa('FS::cust_credit_refund') ) { +% $desc .= ' '. +% '$'. $app->amount. +% ' refunded on '. time2str("%D", $app->_date). +% '<BR>'; +% } else { +% die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund"; +% } +% } +% if ( $cust_credit->credited > 0 ) { +% $desc .= ' <B><FONT COLOR="#FF0000">$'. +% $cust_credit->credited. ' unapplied</FONT></B>'. +% qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!. +% $cust_credit->crednum. +% qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!. +% '<BR>'; +% } +% } +%# +% my $delete = ''; +% if ( $cust_credit->closed !~ /^Y/i +% +% #s'pose deleting a credit isn't bad like deleting a payment +% # and this needs to be generally available until we have credit voiding.. +% #&& $conf->exists('deletecredits') +% +% && $curuser->access_right('Delete credit') +% ) +% { +% $delete = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum. +% qq!', 'Are you sure you want to delete this credit?')">!. +% qq!delete</A>)!; +% } +% +% my $unapply = ''; +% if ( $cust_credit->closed !~ /^Y/i +% && scalar(@cust_credit_bill) +% && $curuser->access_right('Unapply credit') +% ) +% { +% $unapply = qq! (<A HREF="javascript:areyousure('!. +% qq!${p}misc/unapply-cust_credit.cgi?!. $cust_credit->crednum. +% qq!', 'Are you sure you want to unapply this credit?')">!. +% qq!unapply</A>)!; +% } +% +% push @history, { +% 'date' => $cust_credit->_date, +% 'desc' => $pre. "Credit$post by ". $cust_credit->otaker. +% ( $cust_credit->reason +% ? ' ('. $cust_credit->reason. ')' +% : '' +% ). +% "$desc$apply$delete$unapply", +% 'credit' => $cust_credit->amount, +% }; +% +%} +% +%#refunds +%foreach my $cust_refund ($cust_main->cust_refund) { +% +% my $payby = $cust_refund->payby; +% my $payinfo = $payby eq 'CARD' +% ? $cust_refund->payinfo_masked +% : $cust_refund->payinfo; +% +% $payby =~ s/^BILL$/Check #/ if $payinfo; +% $payby =~ s/^CHEK$/Electronic check /; +% $payby =~ s/^(CARD|COMP)$/$1 /; +% +% push @history, { +% 'date' => $cust_refund->_date, +% 'desc' => "Refund ($payby$payinfo) by ". $cust_refund->otaker, +% 'refund' => $cust_refund->refund, +% }; +% +%} +% +% + + +<% include("/elements/table-grid.html") %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Date</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Charge</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Payment</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>In-house<BR>Credit</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Refund</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Balance</FONT></TH> +</TR> +% +%#display payment history +% +%my $balance = 0; +%my %target = (); +%my $money_char = $conf->config('money_char') || '$'; +% +%my $years = $conf->config('payment_history-years') || 2; +%my $older_than = time - $years * 31556736; #60*60*24*365.24 +%my $hidden = 0; +%my $seen = 0; +%my $old_history = 0; +% +%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { +% +% my $display; +% if ( $item->{'date'} < $older_than ) { +% $display = ' STYLE="display:none" '; +% $hidden = 1; +% } else { +% +% $display = ''; +% +% if ( $hidden && ! $seen++ ) { +% ( my $balance_forward = $money_char. $balance ) =~ s/^\$\-/- \$/; +% + + + <TR ID="balance_forward_row"> + <TD CLASS="grid" BGCOLOR="#dddddd"> + <% time2str("%D",$item->{'date'}) %> + </TD> + + <TD CLASS="grid" BGCOLOR="#dddddd"> + <I>Starting balance on <% time2str("%D",$item->{'date'}) %></I> + (<A HREF="javascript:void(0);" onClick="show_history();">show prior history</A>) + </TD> + + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"><I><% $balance_forward %></I></TD> + + </TR> +% +% } +% +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $charge = exists($item->{'charge'}) +% ? sprintf("$money_char\%.2f", $item->{'charge'}) +% : ''; +% +% my $payment = exists($item->{'payment'}) +% ? sprintf("- $money_char\%.2f", $item->{'payment'}) +% : ''; +% +% $payment ||= sprintf( "<DEL>- $money_char\%.2f</DEL>", +% $item->{'void_payment'} +% ) +% if exists($item->{'void_payment'}); +% +% my $credit = exists($item->{'credit'}) +% ? sprintf("- $money_char\%.2f", $item->{'credit'}) +% : ''; +% +% my $refund = exists($item->{'refund'}) +% ? sprintf("$money_char\%.2f", $item->{'refund'}) +% : ''; +% +% my $target = exists($item->{'target'}) ? $item->{'target'} : ''; +% +% $balance += $item->{'charge'} if exists $item->{'charge'}; +% $balance -= $item->{'payment'} if exists $item->{'payment'}; +% $balance -= $item->{'credit'} if exists $item->{'credit'}; +% $balance += $item->{'refund'} if exists $item->{'refund'}; +% $balance = sprintf("%.2f", $balance); +% $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp +% ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/- \$/; +% +% + + + <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% unless ( !$target || $target{$target}++ ) { + + <A NAME="<% $target %>"> +% } + + <% time2str("%D",$item->{'date'}) %> +% if ( $target && $target{$target} == 1 ) { + + </A> +% } + + </FONT> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $item->{'desc'} %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $charge %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $payment %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $credit %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $refund %> + </TD> + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $showbalance %> + </TD> + </TR> +% } + + +</TABLE> + +<SCRIPT TYPE="text/javascript"> + +function show_history () { + //alert('showing history!'); + + var balance_forward_row = document.getElementById('balance_forward_row'); + + balance_forward_row.style.display = 'none'; + for ( var i = 0; i < <% $old_history %>; i++ ) { + var oldRow = document.getElementById('old_history'+i); + oldRow.style.display = ''; + } + +} + +</SCRIPT> + +<%init> + +my( $cust_main ) = @_; +my $custnum = $cust_main->custnum; + +my $conf = new FS::Conf; + +my $curuser = $FS::CurrentUser::CurrentUser; + +my @payby = grep /\w/, $conf->config('payby'); +#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP )) +@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) + unless @payby; +my %payby = map { $_=>1 } @payby; + +</%init> diff --git a/httemplate/view/cust_main/quick-charge.html b/httemplate/view/cust_main/quick-charge.html new file mode 100644 index 000000000..be8b9d838 --- /dev/null +++ b/httemplate/view/cust_main/quick-charge.html @@ -0,0 +1,19 @@ +% +% my( $cust_main ) = @_; +% + + +<FORM ACTION="<%$p%>edit/process/quick-charge.cgi" METHOD="POST"> + +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>"> + +Description:<INPUT TYPE="text" NAME="pkg"> + +Amount:<INPUT TYPE="text" NAME="amount" SIZE=6> + +<% include('/elements/select-taxclass.html') %> + +<INPUT TYPE="submit" VALUE="One-time charge"> + +</FORM> + diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html new file mode 100644 index 000000000..84cc90299 --- /dev/null +++ b/httemplate/view/cust_main/tickets.html @@ -0,0 +1,78 @@ +% +% my( $cust_main ) = @_; +% +% my $conf = new FS::Conf; +% my $num = $conf->config('cust_main-max_tickets') || 10; +% +% my @tickets = (); +% unless ( $conf->config('ticket_system-custom_priority_field') ) { +% +% @tickets = +% @{ FS::TicketSystem->customer_tickets($cust_main->custnum, $num) }; +% +% } else { +% +% foreach my $priority ( +% $conf->config('ticket_system-custom_priority_field-values'), '' +% ) { +% last if scalar(@tickets) >= $num; +% push @tickets, +% @{ FS::TicketSystem->customer_tickets( $cust_main->custnum, +% $num - scalar(@tickets), +% $priority, +% ) +% }; +% } +% +% } +% +% + +<A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A> +<BR> + +(<A HREF="<% FS::TicketSystem->href_customer_tickets($cust_main->custnum) %>">View all tickets for this customer</A>) +(<A HREF="<% FS::TicketSystem->href_new_ticket($cust_main, join(', ', grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list ) ) %>">New ticket for this customer</A>) + +<% include("/elements/table-grid.html") %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% + + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc">#</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Subject</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Priority</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Queue</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> +</TR> +% foreach my $ticket ( @tickets ) { +% my $href = FS::TicketSystem->href_ticket($ticket->{id}); +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + +<TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF=<%$href%>><% $ticket->{id} %></A></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF=<%$href%>><% $ticket->{subject} %></A></TD> + + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{content} || $ticket->{priority} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{name} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $ticket->{status} %></TD> + +</TR> +% } + + +</TABLE> + diff --git a/httemplate/view/cust_pkg.cgi b/httemplate/view/cust_pkg.cgi new file mode 100755 index 000000000..78b42f127 --- /dev/null +++ b/httemplate/view/cust_pkg.cgi @@ -0,0 +1,166 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +% +%my %uiview = (); +%my %uiadd = (); +%foreach my $part_svc ( qsearch('part_svc',{}) ) { +% $uiview{$part_svc->svcpart} = popurl(2). "view/". $part_svc->svcdb . ".cgi"; +% $uiadd{$part_svc->svcpart}= popurl(2). "edit/". $part_svc->svcdb . ".cgi"; +%} +% +%my ($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $pkgnum = $1; +% +%#get package record +%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +%die "No package!" unless $cust_pkg; +%my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); +% +%my $custnum = $cust_pkg->getfield('custnum'); +%print header('Package View', menubar( +% "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", +% 'Main Menu' => popurl(2) +%)); +% +%#print info +%my ($susp,$cancel,$expire)=( +% $cust_pkg->getfield('susp'), +% $cust_pkg->getfield('cancel'), +% $cust_pkg->getfield('expire'), +%); +%my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); +%my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); +%my $otaker = $cust_pkg->getfield('otaker'); +% +%print <<END; +%<SCRIPT> +%function areyousure(href) { +% if (confirm("Permanently delete included services and cancel this package?") == true) +% window.location.href = href; +%} +%</SCRIPT> +%END +% +%print "Package information"; +%print ' (<A HREF="'. popurl(2). 'misc/unsusp_pkg.cgi?'. $pkgnum. +% '">unsuspend</A>)' +% if ( $susp && ! $cancel ); +% +%print ' (<A HREF="'. popurl(2). 'misc/susp_pkg.cgi?'. $pkgnum. +% '">suspend</A>)' +% unless ( $susp || $cancel ); +% +%print ' (<A HREF="javascript:areyousure(\''. popurl(2). 'misc/cancel_pkg.cgi?'. +% $pkgnum. '\')">cancel</A>)' +% unless $cancel; +% +%print ' (<A HREF="'. popurl(2). 'edit/REAL_cust_pkg.cgi?'. $pkgnum. +% '">edit dates</A>)'; +% +%print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2), +% '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">', +% $pkgnum, '</TD></TR>', +% '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">', +% $pkg, '</TD></TR>', +% '<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">', +% $comment, '</TD></TR>', +% '<TR><TD ALIGN="right">Setup date</TD><TD BGCOLOR="#ffffff">', +% ( $setup ? time2str("%D",$setup) : "(Not setup)" ), '</TD></TR>'; +% +%print '<TR><TD ALIGN="right">Last bill date</TD><TD BGCOLOR="#ffffff">', +% ( $cust_pkg->get('last_bill') ? time2str("%D",$cust_pkg->get('last_bill')) : " " ), +% '</TD></TR>' +% if $cust_pkg->dbdef_table->column('last_bill'); +% +%print '<TR><TD ALIGN="right">Next bill date</TD><TD BGCOLOR="#ffffff">', +% ( $bill ? time2str("%D",$bill) : " " ), '</TD></TR>'; +% +%print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">', +% time2str("%D",$susp), '</TD></TR>' if $susp; +%print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">', +% time2str("%D",$expire), '</TD></TR>' if $expire; +%print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">', +% time2str("%D",$cancel), '</TD></TR>' if $cancel; +%print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">', +% $otaker, '</TD></TR>', +% '</TABLE></TD></TR></TABLE>'; +% +%unless ($expire) { +% print <<END; +%<FORM ACTION="../misc/expire_pkg.cgi" METHOD="post"> +%<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> +%Expire (date): <INPUT TYPE="text" NAME="date" VALUE="" > +%<INPUT TYPE="submit" VALUE="Cancel later"> +%END +%} +% +%unless ($cancel) { +% +% #services +% print '<BR>Service Information', &table(); +% +% #list of services this pkgpart includes +% my $pkg_svc; +% my %pkg_svc; +% #foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $cust_pkg->pkgpart }) ) { +% foreach $pkg_svc ( $cust_pkg->part_pkg->pkg_svc ) { +% $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity; +% } +% +% #list of records from cust_svc +% my $svcpart; +% foreach $svcpart (sort {$a <=> $b} keys %pkg_svc) { +% +% my($svc)=qsearchs('part_svc',{'svcpart'=>$svcpart})->getfield('svc'); +% +% my(@cust_svc)=qsearch('cust_svc',{'pkgnum'=>$pkgnum, +% 'svcpart'=>$svcpart, +% }); +% +% my($enum); +% for $enum ( 1 .. $pkg_svc{$svcpart} ) { +% +% my($cust_svc); +% if ( $cust_svc=shift @cust_svc ) { +% my($svcnum)=$cust_svc->svcnum; +% my($label, $value, $svcdb) = $cust_svc->label; +% print <<END; +%<TR><TD><A HREF="$uiview{$svcpart}?$svcnum">(View/Edit) $svc: $value<A></TD></TR> +%END +% } else { +% print qq!<TR><TD>!. +% qq!<A HREF="$uiadd{$svcpart}?pkgnum$pkgnum-svcpart$svcpart">!. +% qq!(Provision) $svc</A>!; +% +% print qq! or <A HREF="../misc/link.cgi?pkgnum$pkgnum-svcpart$svcpart">!. +% qq!(Link to legacy) $svc</A>! +% if $conf->exists('legacy_link'); +% +% print '</TD></TR>'; +% } +% +% } +% warn "WARNING: Leftover services pkgnum $pkgnum!" if @cust_svc;; +% } +% +% print "</TABLE><FONT SIZE=-1>", +% "Choose (View/Edit) to view or edit an existing service<BR>", +% "Choose (Provision) to setup a new service<BR>"; +% +% print "Choose (Link to legacy) to link to a legacy (pre-Freeside) service" +% if $conf->exists('legacy_link'); +% +% print "</FONT>"; +%} +% +%#formatting +%print <<END; +% </BODY> +%</HTML> +%END +% +% + diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html new file mode 100644 index 000000000..35434632e --- /dev/null +++ b/httemplate/view/elements/svc_Common.html @@ -0,0 +1,116 @@ +% +% +% # options example... +% # +% # 'table' => 'svc_something' +% # +% # 'labels' => { +% # 'column' => 'Label', +% # }, +% # +% # listref - each item is a literal column name (or method) or (notyet) coderef +% # if not specified all columns (except for the primary key) will be viewable +% # 'fields' => [ +% # ] +% +% my(%opt) = @_; +% +% my $table = $opt{'table'}; +% +% my $fields = $opt{'fields'} +% #|| [ grep { $_ ne 'svcnum' } dbdef->table($table)->columns ]; +% || [ grep { $_ ne 'svcnum' } fields($table) ]; +% +% my($query) = $cgi->keywords; +% $query =~ /^(\d+)$/; +% my $svcnum = $1; +% my $svc_x = qsearchs( $opt{'table'}, { 'svcnum' => $svcnum } ) +% or die "Unknown svcnum $svcnum in ". $opt{'table'}. " table\n"; +% +% my $cust_svc = $svc_x->cust_svc; +% my($label, $value, $svcdb) = $cust_svc->label; +% +% my $pkgnum = $cust_svc->pkgnum; +% +% my($cust_pkg, $custnum); +% if ($pkgnum) { +% $cust_pkg = $cust_svc->cust_pkg; +% $custnum = $cust_pkg->custnum; +% } else { +% $cust_pkg = ''; +% $custnum = ''; +% } +% +% +% if ( $custnum ) { + + + <% include("/elements/header.html","View $label: $value", menubar( + "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + )) %> + + <% include( '/elements/small_custview.html', $custnum, '', 1 ) %> + <BR> +% } else { + + + <SCRIPT> + function areyousure(href) { + if (confirm("Permanently delete this <% $label %>?") == true) + window.location.href = href; + } + </SCRIPT> + + <% include("/elements/header.html","View $label: $value", menubar( + "Cancel this (unaudited) $label" => + "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')" + )) %> +% } + + +Service #<B><% $svcnum %></B> +| <A HREF="<%$p%>edit/<% $opt{'table'} %>.cgi?<%$svcnum%>">Edit this <% $label %></A> +<BR> + +<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +% foreach my $f ( @$fields ) { +% +% my( $field, $type); +% if ( ref($f) ) { +% $field = $f->{'field'}, +% $type = $f->{'type'} || 'text', +% } else { +% $field = $f; +% $type = 'text'; +% } +% + + + <TR> + <TD ALIGN="right"> + <% ( $opt{labels} && exists $opt{labels}->{$field} ) + ? $opt{labels}->{$field} + : $field + %> + </TD> +% +% #eventually more options for <SELECT>, etc. fields +% + + + <TD BGCOLOR="#ffffff"><% $svc_x->$field %><TD> + + </TR> +% } +% foreach (sort { $a cmp $b } $svc_x->virtual_fields) { + + <% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %> +% } + + +</TABLE></TD></TR></TABLE> + +<BR> +<% joblisting({'svcnum'=>$svcnum}, 1) %> + +<% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi new file mode 100755 index 000000000..9c2fbc94d --- /dev/null +++ b/httemplate/view/svc_acct.cgi @@ -0,0 +1,341 @@ +% +% +%my $conf = new FS::Conf; +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +%die "Unknown svcnum" unless $svc_acct; +% +%#false laziness w/all svc_*.cgi +%my $cust_svc = qsearchs( 'cust_svc' , { 'svcnum' => $svcnum } ); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); +% $custnum = $cust_pkg->custnum; +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +%#eofalse +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); +%die "Unknown svcpart" unless $part_svc; +%my $svc = $part_svc->svc; +% +%die 'Empty domsvc for svc_acct.svcnum '. $svc_acct->svcnum +% unless $svc_acct->domsvc; +%my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc } ); +%die 'Unknown domain (domsvc '. $svc_acct->domsvc. +% ' for svc_acct.svcnum '. $svc_acct->svcnum. ')' +% unless $svc_domain; +%my $domain = $svc_domain->domain; +% +% +% if ( $custnum ) { + + + <% include("/elements/header.html","View $svc account", menubar( + "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + "Main menu" => $p, + )) %> + + <% include( '/elements/small_custview.html', $custnum, '', 1 ) %> + <BR> +% } else { + + + <SCRIPT> + function areyousure(href) { + if (confirm("Permanently delete this account?") == true) + window.location.href = href; + } + </SCRIPT> + + <% include("/elements/header.html",'Account View', menubar( + "Cancel this (unaudited) account" => + "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')", + "Main menu" => $p, + )) %> +% } +% if ( $part_svc->part_export_usage ) { +% +% my $last_bill; +% my %plandata; +% if ( $cust_pkg ) { +% #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really +% #belong in plan data +% %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } +% split("\n", $cust_pkg->part_pkg->plandata ); +% +% $last_bill = $cust_pkg->last_bill; +% } else { +% $last_bill = 0; +% %plandata = (); +% } +% +% my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time ); +% my $hour = int($seconds/3600); +% my $min = int( ($seconds%3600) / 60 ); +% my $sec = $seconds%60; +% +% my $input = $svc_acct->attribute_since_sqlradacct( +% $last_bill, time, 'AcctInputOctets' +% ) / 1048576; +% my $output = $svc_acct->attribute_since_sqlradacct( +% $last_bill, time, 'AcctOutputOctets' +% ) / 1048576; +% +% + + + RADIUS session information<BR> + <% ntable('#cccccc',2) %> + <TR><TD BGCOLOR="#ffffff"> +% if ( $seconds ) { + + Online <B><% $hour %></B>h <B><% $min %></B>m <B><% $sec %></B>s +% } else { + + Has not logged on +% } +% if ( $cust_pkg ) { + + since last bill (<% time2str('%a %b %o %Y', $last_bill) %>) +% if ( length($plandata{recur_included_hours}) ) { + + - <% $plandata{recur_included_hours} %> total hours in plan +% } + + <BR> +% } else { + + (no billing cycle available for unaudited account)<BR> +% } + + + Upload: <B><% sprintf("%.3f", $input) %></B> megabytes<BR> + Download: <B><% sprintf("%.3f", $output) %></B> megabytes<BR> +% my $href = qq!<A HREF="${p}search/sqlradius.cgi?svcnum=$svcnum!; + + View session detail: + <% $href %>;begin=<% $last_bill %>">this billing cycle</A> + | <% $href %>;begin=<% time-15552000 %>">past six months</A> + | <% $href %>">all sessions</A> + + </TD></TR></TABLE><BR> +% } + + +<SCRIPT TYPE="text/javascript"> +function enable_change () { + if ( document.OneTrueForm.svcpart.selectedIndex > 1 ) { + document.OneTrueForm.submit.disabled = false; + } else { + document.OneTrueForm.submit.disabled = true; + } +} +</SCRIPT> +<FORM NAME="OneTrueForm" ACTION="<%$p%>edit/process/cust_svc.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +% #print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!; +% +% my @part_svc = (); +% if ( $pkgnum ) { +% @part_svc = grep { $_->svcdb eq 'svc_acct' +% && $_->svcpart != $part_svc->svcpart } +% $cust_pkg->available_part_svc; +% } else { +% @part_svc = qsearch('part_svc', { +% svcdb => 'svc_acct', +% disabled => '', +% svcpart => { op=>'!=', value=>$part_svc->svcpart }, +% } ); +% } +% + + +Service #<B><% $svcnum %></B> +| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A> +% if ( @part_svc ) { + +| <SELECT NAME="svcpart" onChange="enable_change()"> + <OPTION VALUE="">Change service</OPTION> + <OPTION VALUE="">--------------</OPTION> +% foreach my $opt_part_svc ( @part_svc ) { + + <OPTION VALUE="<% $opt_part_svc->svcpart %>"><% $opt_part_svc->svc %></OPTION> +% } + + </SELECT> + <INPUT NAME="submit" TYPE="submit" VALUE="Change" disabled> +% } + + +<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> + +<TR> + <TD ALIGN="right">Service</TD> + <TD BGCOLOR="#ffffff"><% $part_svc->svc %></TD> +</TR> +<TR> + <TD ALIGN="right">Username</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->username %></TD> +</TR> +<TR> + <TD ALIGN="right">Domain</TD> + <TD BGCOLOR="#ffffff"><% $domain %></TD> +</TR> + +<TR> + <TD ALIGN="right">Password</TD> + <TD BGCOLOR="#ffffff"> +% my $password = $svc_acct->_password; +% if ( $password =~ /^\*\w+\* (.*)$/ ) { +% $password = $1; +% + + <I>(login disabled)</I> +% } +% if ( $conf->exists('showpasswords') ) { + + <PRE><% encode_entities($password) %></PRE> +% } else { + + <I>(hidden)</I> +% } + + + </TD> +</TR> +% $password = ''; +% if ( $conf->exists('security_phrase') ) { +% my $sec_phrase = $svc_acct->sec_phrase; +% + + <TR> + <TD ALIGN="right">Security phrase</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->sec_phrase %></TD> + </TR> +% } +% if ( $svc_acct->popnum ) { +% my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum}); +% + + <TR> + <TD ALIGN="right">Access number</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct_pop->text %></TD> + </TR> +% } +% if ($svc_acct->uid ne '') { + + <TR> + <TD ALIGN="right">UID</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->uid %></TD> + </TR> +% } +% if ($svc_acct->gid ne '') { + + <TR> + <TD ALIGN="right">GID</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->gid %></TD> + </TR> +% } +% if ($svc_acct->finger ne '') { + + <TR> + <TD ALIGN="right">GECOS</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->finger %></TD> + </TR> +% } +% if ($svc_acct->dir ne '') { + + <TR> + <TD ALIGN="right">Home directory</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->dir %></TD> + </TR> +% } +% if ($svc_acct->shell ne '') { + + <TR> + <TD ALIGN="right">Shell</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->shell %></TD> + </TR> +% } +% if ($svc_acct->quota ne '') { + + <TR> + <TD ALIGN="right">Quota</TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->quota %></TD> + </TR> +% } +% if ($svc_acct->slipip) { + + <TR> + <TD ALIGN="right">IP address</TD> + <TD BGCOLOR="#ffffff"> + <% ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' ) + ? "<I>(Dynamic)</I>" + : $svc_acct->slipip + %> + </TD> + </TR> +% } +% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) { +% $attribute =~ /^radius_(.*)$/; +% my $pattribute = $FS::raddb::attrib{$1}; +% + + <TR> + <TD ALIGN="right">Radius (reply) <% $pattribute %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD> + </TR> +% } +% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) { +% $attribute =~ /^rc_(.*)$/; +% my $pattribute = $FS::raddb::attrib{$1}; +% + + <TR> + <TD ALIGN="right">Radius (check) <% $pattribute %></TD> + <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD> + </TR> +% } + + +<TR> + <TD ALIGN="right">RADIUS groups</TD> + <TD BGCOLOR="#ffffff"><% join('<BR>', $svc_acct->radius_groups) %></TD> +</TR> +% if ( $svc_acct->seconds =~ /^\d+$/ ) { + + <TR> + <TD ALIGN="right">Prepaid time</TD> + <TD BGCOLOR="#ffffff"><% duration_exact($svc_acct->seconds) %></TD> + </TR> +% } +% +%# Can this be abstracted further? Maybe a library function like +%# widget('HTML', 'view', $svc_acct) ? It would definitely make UI +%# style management easier. +% +% foreach (sort { $a cmp $b } $svc_acct->virtual_fields) { + + <% $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %> +% } + + +</TABLE></TD></TR></TABLE> +</FORM> +<BR><BR> + +<% join("<BR>", $conf->config('svc_acct-notes') ) %> +<BR><BR> + +<% joblisting({'svcnum'=>$svcnum}, 1) %> + +</BODY> +</HTML> diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi new file mode 100644 index 000000000..1800541f0 --- /dev/null +++ b/httemplate/view/svc_broadband.cgi @@ -0,0 +1,163 @@ +<!-- mason kludge --> +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_broadband = qsearchs( 'svc_broadband', { 'svcnum' => $svcnum } ) +% or die "svc_broadband: Unknown svcnum $svcnum"; +% +%#false laziness w/all svc_*.cgi +%my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); +% $custnum = $cust_pkg->custnum; +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +%#eofalse +% +%my $addr_block = $svc_broadband->addr_block; +%my $router = $addr_block->router; +% +%if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" }; +% +%my ( +% $routername, +% $routernum, +% $speed_down, +% $speed_up, +% $ip_addr, +% $ip_gateway, +% $ip_netmask, +% ) = ( +% $router->getfield('routername'), +% $router->getfield('routernum'), +% $svc_broadband->getfield('speed_down'), +% $svc_broadband->getfield('speed_up'), +% $svc_broadband->getfield('ip_addr'), +% $addr_block->ip_gateway, +% $addr_block->NetAddr->mask, +% ); +% + + +<%include("/elements/header.html",'Broadband Service View', menubar( + ( ( $custnum ) + ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + ) + : ( "Cancel this (unaudited) website" => + "${p}misc/cancel-unaudited.cgi?$svcnum" ) + ), + "Main menu" => $p, +)) +%> + +<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> + <TD ALIGN="right">IP Netmask</TD> + <TD BGCOLOR="#ffffff"><%$ip_netmask%></TD> + </TR> + <TR> + <TD ALIGN="right">IP Gateway</TD> + <TD BGCOLOR="#ffffff"><%$ip_gateway%></TD> + </TR> + <TR COLSPAN="2"><TD></TD></TR> +% +%foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) { +% print $svc_broadband->pvf($_)->widget('HTML', 'view', +% $svc_broadband->getfield($_)), "\n"; +%} +% +% + + </TABLE> + </TD> + </TR> +</TABLE> + +<BR> +<%ntable("#cccccc", 2)%> +% +% my $sb_router = qsearchs('router', { svcnum => $svcnum }); +% if ($sb_router) { +% + + <B>Router associated: <%$sb_router->routername%> </B> + <A HREF="<%popurl(2)%>edit/router.cgi?<%$sb_router->routernum%>"> + (details) + </A> + <BR> +% my @sb_addr_block; +% if (@sb_addr_block = $sb_router->addr_block) { +% + + <B>Address space </B> + <A HREF="<%popurl(2)%>browse/addr_block.cgi"> + (edit) + </A> + <BR> +% print ntable("#cccccc", 1); +% foreach (@sb_addr_block) { + + <TR> + <TD><%$_->ip_gateway%>/<%$_->ip_netmask%></TD> + </TR> +% } + + </TABLE> +% } else { + + <B>No address space allocated.</B> +% } + + <BR> +% +% } else { +% + + +<FORM METHOD="GET" ACTION="<%popurl(2)%>edit/router.cgi"> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> +Add router named + <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%$svcnum%>)"> + <INPUT TYPE="submit" VALUE="Add router"> +</FORM> +% +%} +% + + +<BR> +<%joblisting({'svcnum'=>$svcnum}, 1)%> + </BODY> +</HTML> + diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi new file mode 100755 index 000000000..44390bf8b --- /dev/null +++ b/httemplate/view/svc_domain.cgi @@ -0,0 +1,116 @@ +<!-- mason kludge --> +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_domain = qsearchs('svc_domain',{'svcnum'=>$svcnum}); +%die "Unknown svcnum" unless $svc_domain; +% +%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->getfield('custnum'); +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); +%die "Unknown svcpart" unless $part_svc; +% +%my $email = ''; +%if ($svc_domain->catchall) { +% my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } ); +% die "Unknown svcpart" unless $svc_acct; +% $email = $svc_acct->email; +%} +% +%my $domain = $svc_domain->domain; +% +% + + +<% include("/elements/header.html",'Domain View', menubar( + ( ( $pkgnum || $custnum ) + ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", + ) + : ( "Delete this (unaudited) domain" => + "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum', 'Delete $domain and all records?' )" ) + ), + "Main menu" => $p, +)) %> + +Service #<% $svcnum %> +<BR>Service: <B><% $part_svc->svc %></B> +<BR>Domain name: <B><% $domain %></B> +<BR>Catch all email <A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>: +<% $email ? "<B>$email</B>" : "<I>(none)<I>" %> +<BR><BR><A HREF="<% ${p} %>misc/whois.cgi?custnum=<%$custnum%>;svcnum=<%$svcnum%>;domain=<%$domain%>">View whois information.</A> +<BR><BR> +<SCRIPT> + function areyousure(href, message) { + if ( confirm(message) == true ) + window.location.href = href; + } + function slave_areyousure() { + return confirm("Remove all records and slave from " + document.SlaveForm.recdata.value + "?"); + } +</SCRIPT> +% my @records; if ( @records = $svc_domain->domain_record ) { + + <% ntable("",2) %> + <tr><th>Zone</th><th>Type</th><th>Data</th></tr> +% foreach my $domain_record ( @records ) { +% my $type = $domain_record->rectype eq '_mstr' +% ? "(slave)" +% : $domain_record->recaf. ' '. $domain_record->rectype; +% + + + <tr><td><% $domain_record->reczone %></td> + <td><% $type %></td> + <td><% $domain_record->recdata %> +% unless ( $domain_record->rectype eq 'SOA' ) { + + (<A HREF="javascript:areyousure('<%$p%>misc/delete-domain_record.cgi?<%$domain_record->recnum%>', 'Delete \'<% $domain_record->reczone %> <% $type %> <% $domain_record->recdata %>\' ?' )">delete</A>) +% } + + </td></tr> +% } + + </table> +% } + + +<BR> +<FORM METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> +<INPUT TYPE="text" NAME="reczone"> +<INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN + <SELECT NAME="rectype"> +% foreach (qw( A NS CNAME MX PTR TXT) ) { + + <OPTION VALUE="<%$_%>"><%$_%></OPTION> +% } + + </SELECT> +<INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Add record"> +</FORM><BR><BR>or<BR><BR> +<FORM NAME="SlaveForm" METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi"> +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> +% if ( @records ) { + Delete all records and +% } + +Slave from nameserver IP +<INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>"> +<INPUT TYPE="hidden" NAME="reczone" VALUE="@"> +<INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> +<INPUT TYPE="hidden" NAME="rectype" VALUE="_mstr"> +<INPUT TYPE="text" NAME="recdata"> <INPUT TYPE="submit" VALUE="Slave domain" onClick="return slave_areyousure()"> +</FORM> +<BR><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..06302bd8f --- /dev/null +++ b/httemplate/view/svc_external.cgi @@ -0,0 +1,55 @@ +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_external = qsearchs( 'svc_external', { 'svcnum' => $svcnum } ) +% or die "svc_external: Unknown svcnum $svcnum"; +% +%my $conf = new FS::Conf; +% +%#false laziness w/all svc_*.cgi +%my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); +% $custnum = $cust_pkg->custnum; +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +%#eofalse +% +% +% + + +<% include("/elements/header.html",'External Service View', menubar( + ( ( $custnum ) + ? ( "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"><% FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TD> + <TD BGCOLOR="#ffffff"><% $conf->config('svc_external-display_type') eq 'artera_turbo' ? sprintf('%010d', $svc_external->id) : $svc_external->id %></TD></TR> +<TR><TD ALIGN="right"><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TD> + <TD BGCOLOR="#ffffff"><% $svc_external->title %></TD></TR> +% foreach (sort { $a cmp $b } $svc_external->virtual_fields) { + + <% $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %> +% } + + +</TABLE></TD></TR></TABLE> +<BR><% joblisting({'svcnum'=>$svcnum}, 1) %> +</BODY></HTML> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi new file mode 100755 index 000000000..fcc0bc96f --- /dev/null +++ b/httemplate/view/svc_forward.cgi @@ -0,0 +1,85 @@ +<!-- mason kludge --> +% +% +%my $conf = new FS::Conf; +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_forward = qsearchs('svc_forward',{'svcnum'=>$svcnum}); +%die "Unknown svcnum" unless $svc_forward; +% +%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->getfield('custnum'); +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +% +%my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ) +% or die "Unkonwn svcpart"; +% +%print header('Mail Forward View', menubar( +% ( ( $pkgnum || $custnum ) +% ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +% ) +% : ( "Cancel this (unaudited) mail forward" => +% "${p}misc/cancel-unaudited.cgi?$svcnum" ) +% ), +% "Main menu" => $p, +%)); +% +%my($srcsvc,$dstsvc,$dst) = ( +% $svc_forward->srcsvc, +% $svc_forward->dstsvc, +% $svc_forward->dst, +%); +%my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : ''; +% +%my $svc = $part_svc->svc; +% +%my $source; +%if ($srcsvc) { +% my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc}) +% or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc"; +% $source = $svc_acct->email; +%} else { +% $source = $src; +%} +% +%my $destination; +%if ($dstsvc) { +% my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc}) +% or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc"; +% $destination = $svc_acct->email; +%} else { +% $destination = $dst; +%} +% +%print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!. +% ntable("#cccccc",2). +% '<TR><TD ALIGN="right">Service number</TD>'. +% qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!. +% '<TR><TD ALIGN="right">Service</TD>'. +% qq!<TD BGCOLOR="#ffffff">$svc</TD></TR>!. +% qq!<TR><TD ALIGN="right">Email to</TD>!. +% qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!. +% qq!<TR><TD ALIGN="right">Forwards to </TD>!. +% qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!; +% +%foreach (sort { $a cmp $b } $svc_forward->virtual_fields) { +% print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)), +% "\n"; +%} +% +%print qq! </TABLE>!. +% '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). +% '</BODY></HTML>' +%; +% +% + diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi new file mode 100644 index 000000000..732f3cd79 --- /dev/null +++ b/httemplate/view/svc_phone.cgi @@ -0,0 +1,10 @@ +<% include('elements/svc_Common.html', + 'table' => 'svc_phone', + 'fields' => [qw( countrycode phonenum )], #pin + 'labels' => { + 'countrycode' => 'Country code', + 'phonenum' => 'Phone number', + 'pin' => 'PIN', + }, + ) +%> diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi new file mode 100644 index 000000000..f2be5c717 --- /dev/null +++ b/httemplate/view/svc_www.cgi @@ -0,0 +1,74 @@ +<!-- mason kludge --> +% +% +%my($query) = $cgi->keywords; +%$query =~ /^(\d+)$/; +%my $svcnum = $1; +%my $svc_www = qsearchs( 'svc_www', { 'svcnum' => $svcnum } ) +% or die "svc_www: Unknown svcnum $svcnum"; +% +%#false laziness w/all svc_*.cgi +%my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } ); +%my $pkgnum = $cust_svc->getfield('pkgnum'); +%my($cust_pkg, $custnum); +%if ($pkgnum) { +% $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } ); +% $custnum = $cust_pkg->custnum; +%} else { +% $cust_pkg = ''; +% $custnum = ''; +%} +%#eofalse +% +%my $usersvc = $svc_www->usersvc; +%my $svc_acct = ''; +%my $email = ''; +%if ( $usersvc ) { +% $svc_acct = qsearchs('svc_acct', { 'svcnum' => $usersvc } ) +% or die "svc_www: Unknown usersvc $usersvc"; +% $email = $svc_acct->email; +%} +% +%my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } ) +% or die "svc_www: Unknown recnum ". $svc_www->recnum; +% +%my $www = $domain_record->zone; +% +%print header('Website View', menubar( +% ( ( $custnum ) +% ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +% ) +% : ( "Cancel this (unaudited) website" => +% "${p}misc/cancel-unaudited.cgi?$svcnum" ) +% ), +% "Main menu" => $p, +%)). +% qq!<A HREF="${p}edit/svc_www.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">Website name</TD>!. +% qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!. +% qq!<TR><TD ALIGN="right">Account</TD>!. +% qq!<TD BGCOLOR="#ffffff">!; +% +%if ( $usersvc ) { +% print qq!<A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A>!; +%} else { +% print '</i>(none)</i>'; +%} +% +%print '</TD></TR>'; +% +%foreach (sort { $a cmp $b } $svc_www->virtual_fields) { +% print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)), +% "\n"; +%} +% +% +%print '</TABLE></TD></TR></TABLE>'. +% '<BR>'. joblisting({'svcnum'=>$svcnum}, 1). +% '</BODY></HTML>' +%; +% + |