diff options
Diffstat (limited to 'httemplate/browse')
32 files changed, 4158 insertions, 0 deletions
diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html new file mode 100644 index 000000000..aa9097f36 --- /dev/null +++ b/httemplate/browse/access_group.html @@ -0,0 +1,106 @@ +<% include( 'elements/browse.html', + 'title' => 'Employee Groups', + 'menubar' => [ 'View Employees' => $p.'browse/access_user.html', ], + 'html_init' => $html_init, + 'name' => 'employee 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, + '', + '', + ], + ) +%> +<%once> + +my $html_init = + "Employee groups control access to the back-office interface. Each employee can be assigned to one or more groups.<BR><BR>". + qq!<A HREF="${p}edit/access_group.html"><I>Add an employee 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, + + ]; + +}; + +tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_info; + +my $rights_sub = sub { + my $access_group = shift; + + #[ map { my $access_right = $_; + # [ + # { + # 'data' => $access_right->rightname, + # 'align' => 'left', + # }, + # ]; + # } + # $access_group->access_rights, + #]; + + #some false laziness w/edit/access_group.html + my $columns = 3; + my $count = 0; + + #include('/elements/table-grid.html', bgcolor=>'#cccccc' ). + '<TABLE>'. + '<TR>'. join( '', map { + + '<TD CLASS="inv" VALIGN="top"><TABLE WIDTH=100%>'. + '<TR><TH BGCOLOR="#dcdcdc">'. $_. '</TH></TR>'. + '<TR><TD>'. + + join('<BR>', grep { $access_group->access_right($_); } + map { ref($_) ? $_->{'rightname'} : $_; } + @{ $rights{$_} } + ). + + '</TD></TR></TABLE></TD>'. + ( ++$count % $columns ? '' : '</TR><TR>') + + } keys %rights ). '</TR></TABLE>'; + +}; + +my $count_query = 'SELECT COUNT(*) FROM access_group'; + +my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html new file mode 100644 index 000000000..321025b69 --- /dev/null +++ b/httemplate/browse/access_user.html @@ -0,0 +1,61 @@ +<% include( 'elements/browse.html', + 'title' => 'Employees', + 'menubar' => [ 'View Employee groups' => $p.'browse/access_group.html', ], + 'html_init' => $html_init, + 'name' => 'employees', + 'disableable' => 1, + 'disabled_statuspos' => 2, + 'query' => { 'table' => 'access_user', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY last, first' + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'align' => $align, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + "Employees have access to the back-office interface. Typically, this is your employees and contractors. In a virtualized setup, you can also add accounts for your reseller's employees.<BR><BR>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 employee</I></A><BR><BR>!; + +#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 $count_query = 'SELECT COUNT(*) FROM access_user'; + +my $link = [ $p.'edit/access_user.html?', 'usernum' ]; + +my @header = ( '#', 'Username', 'Full name', 'Groups' ); +my @fields = ( 'usernum', 'username', 'name', $groups_sub ); +my $align = 'rlll'; +my @links = ( $link, $link, $link, '' ); + +</%init> diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi new file mode 100644 index 000000000..1bbcdcbc1 --- /dev/null +++ b/httemplate/browse/addr_block.cgi @@ -0,0 +1,145 @@ +<% include('elements/browse.html', + 'title' => 'Address Blocks', + 'name' => 'address block', + 'html_init' => $html_init, + 'html_foot' => $html_foot, + 'query' => { 'table' => 'addr_block', + 'hashref' => {}, + 'extra_sql' => $extra_sql, + 'order_by' => $order_by, + }, + 'count_query' => "SELECT count(*) from addr_block $count_sql", + 'header' => [ 'Address Block', + 'Router', + 'Action(s)', + '', + '', + ], + 'fields' => [ 'NetAddr', + sub { my $block = shift; + my $router = $block->router; + my $result = ''; + if ($router) { + $result .= $router->routername. ' ('; + $result .= scalar($block->svc_broadband). ' services)'; + } + $result; + }, + $allocate_text, + sub { shift->router ? '' : '<FONT SIZE="-2">(split)</FONT>' }, + sub { '<FONT SIZE="-2">('. (shift->manual_flag ? 'allow' : 'prevent'). ' automatic ip assignment)</FONT>' }, + ], + 'links' => [ '', + '', + [ 'javascript:void(0)', '' ], + $split_link, + $autoassign_link, + ], + 'link_onclicks' => [ '', + '', + $allocate_link, + '', + ], + 'cell_styles' => [ '', + '', + 'border-right:none;', + 'border-left:none;', + ], + 'agent_virt' => 1, + 'agent_null_right' => 'Broadband global configuration', + 'agent_pos' => 1, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Broadband configuration') + || $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration'); + +my $p2 = popurl(2); +my $path = $p2 . "edit/process/addr_block"; + +my $extra_sql = ""; + +my $count_sql = "WHERE ". $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'Broadband global configuration', +); + +my $order_by = "ORDER BY "; +$order_by .= "inet(ip_gateway), " if driver_name =~ /^Pg/i; +$order_by .= "inet_aton(ip_gateway), " if driver_name =~ /^mysql/i; +$order_by .= "ip_netmask"; + +my $html_init = qq( +<SCRIPT> + function addr_block_areyousure(href, word) { + if(confirm("Are you sure you want to "+word+" this address block?") == true) + window.location.href = href; + } +</SCRIPT> +); + +$html_init .= include('/elements/error.html'); + +my $confirm = sub { + my ($verb, $num) = (shift, shift); + "javascript:addr_block_areyousure('$path/$verb.cgi?blocknum=$num', '$verb')"; +}; + +my $html_foot = qq( + <FORM ACTION="$path/add.cgi" METHOD="POST"> + Gateway/Netmask: + <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2"> +); +$html_foot .= include( '/elements/select-agent.html', + 'agent_null_right' => 'Broadband global configuration', + ); +$html_foot .= qq( + <INPUT TYPE="submit" NAME="submit" VALUE="Add"> + </FORM> +); + +my $allocate_text = sub { my $block = shift; + my $router = $block->router; + my $result = ''; + if ($router) { + $result = '<FONT SIZE="-2">(deallocate)</FONT>' + unless scalar($block->svc_broadband); + }else{ + $result .= '<FONT SIZE="-2">(allocate)</FONT>' + } + $result; +}; + +my $allocate_link = sub { + my $block = shift; + if ($block->router) { + if (scalar($block->svc_broadband) == 0) { + &{$confirm}('deallocate', $block->blocknum); + } else { + ""; + } + } else { + include( '/elements/popup_link_onclick.html', + 'action' => "${p2}edit/allocate.html?blocknum=". $block->blocknum, + 'actionlabel' => 'Allocate block to router', + ); + } +}; + +my $split_link = sub { + my $block = shift; + my $ref = [ '', '' ]; + $ref = [ &{$confirm}('split', $block->blocknum), '' ] + unless ($block->router); + $ref; +}; + +my $autoassign_link = sub { + my $block = shift; + my $url = "$path/manual_flag.cgi?manual_flag="; + $url .= $block->manual_flag ? '' : 'Y'; + [ "$url;blocknum=", 'blocknum' ]; +}; + +</%init> diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi new file mode 100755 index 000000000..0a516edef --- /dev/null +++ b/httemplate/browse/agent.cgi @@ -0,0 +1,422 @@ +<% include("/elements/header.html",'Agent Listing', menubar( + '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">Master Customer</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice<BR>Template</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH> + <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"><FONT SIZE=-1>Registration<BR>codes</FONT></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>Configuration Overrides</FONT></TH> +</TR> +% +%# <TH><FONT SIZE=-1>Agent #</FONT></TH> +%# <TH>Agent</TH> +% +%foreach my $agent ( sort { +% #$a->getfield('agentnum') <=> $b->getfield('agentnum') +% $a->getfield('agent') cmp $b->getfield('agent') +%} 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 %>"> +% if ( $agent->agent_custnum ) { + <% include('/elements/small_custview.html', + $agent->agent_custnum, + scalar($conf->config('countrydefault')), + 1, #show balance + ) + %> +% } + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% foreach my $access_group ( +% map $_->access_group, +% qsearch('access_groupagent', { 'agentnum' => $agent->agentnum }) +% ) { + <A HREF="<%$p%>edit/access_group.html?<% $access_group->groupnum %>"><% $access_group->groupname |h %><BR> +% } + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $agent->invoice_template || '(Default)' %> + </TD> + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="bottom"> + <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 %>graph/report_cust_pkg.html?agentnum=<% $agent->agentnum %>">Package Churn</A> + <BR><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="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" 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?<% $override->agentgatewaynum %>">(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="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> +% foreach my $override ( +% qsearch('conf', { 'agentnum' => $agent->agentnum } ) +% ) { +% + + <TR> + <TD> + <% $override->name %> <FONT SIZE=-1><A HREF="<%$p%>config/config-delete.cgi?<% $override->confnum %>">(delete)</A></FONT> + </TD> + </TR> +% } + + <TR> + <TD><FONT SIZE=-1><A HREF="<%$p%>config/config-view.cgi?agentnum=<% $agent->agentnum %>">(view/add/edit overrides)</A></FONT></TD> + </TR> + </TABLE> + </TD> + + </TR> +% } + + + </TABLE> + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %search; +if ( $cgi->param('showdisabled') + || !dbdef->table('agent')->column('disabled') ) { + %search = (); +} else { + %search = ( 'disabled' => '' ); +} + +my $conf = new FS::Conf; + +</%init> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi new file mode 100755 index 000000000..d64ff186a --- /dev/null +++ b/httemplate/browse/agent_type.cgi @@ -0,0 +1,61 @@ +<% include( 'elements/browse.html', + 'title' => 'Agent Types', + 'menubar' => [ '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, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +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' ]; + +</%init> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi new file mode 100755 index 000000000..736d7fdbe --- /dev/null +++ b/httemplate/browse/cust_main_county.cgi @@ -0,0 +1,454 @@ +<% include( 'elements/browse.html', + 'title' => "Tax Rates $title", + 'name_singular' => 'tax rate', + 'menubar' => \@menubar, + 'html_init' => $html_init, + 'html_posttotal' => $html_posttotal, + 'html_form' => '<FORM NAME="taxesForm">', + 'html_foot' => $html_foot, + 'query' => { + 'table' => 'cust_main_county', + 'hashref' => $hashref, + 'order_by' => + 'ORDER BY country, state, county, taxclass', + }, + 'count_query' => $count_query, + 'header' => \@header, + 'header2' => \@header2, + 'fields' => \@fields, + 'align' => $align, + 'color' => \@color, + 'cell_style' => \@cell_style, + 'links' => \@links, + 'link_onclicks' => \@link_onclicks, + ) +%> +% +% # <FONT SIZE=-1><A HREF="<% $p %>edit/process/cust_main_county-collapse.cgi?<% $hashref->{taxnum} %>">collapse state</A></FONT> +% # % } +% +<%once> + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $exempt_sub = sub { + my $cust_main_county = shift; + + my @exempt = (); + push @exempt, + sprintf("$money_char%.2f per month", $cust_main_county->exempt_amount ) + if $cust_main_county->exempt_amount > 0; + + push @exempt, 'Setup fee' + if $cust_main_county->setuptax =~ /^Y$/i; + + push @exempt, 'Recurring fee' + if $cust_main_county->recurtax =~ /^Y$/i; + + [ map [ {'data'=>$_} ], @exempt ]; +}; + +my $oldrow; +my $cell_style; +my $cell_style_sub = sub { + my $row = shift; + if ( $oldrow ne $row ) { + if ( $oldrow ) { + if ( $oldrow->country ne $row->country ) { + $cell_style = 'border-top:1px solid #000000'; + } elsif ( $oldrow->state ne $row->state ) { + $cell_style = 'border-top:1px solid #cccccc'; #default? + } elsif ( $oldrow->state eq $row->state ) { + #$cell_style = 'border-top:dashed 1px dark gray'; + $cell_style = 'border-top:1px dashed #cccccc'; + } + } + $oldrow = $row; + } + return $cell_style; +}; + +#my $edit_link = [ "${p}edit/cust_main_county.html", 'taxnum' ]; +my $edit_link = [ 'javascript:void(0);', sub { ''; } ]; + +my $edit_onclick = sub { + my $row = shift; + my $taxnum = $row->taxnum; + include( '/elements/popup_link_onclick.html', + 'action' => "${p}edit/cust_main_county.html?$taxnum", + 'actionlabel' => 'Edit tax rate', + 'height' => 420, + #default# 'width' => 540, + #default# 'color' => '#333399', + ); +}; + +sub expand_link { + my %param = @_; + + my $taxnum = $param{'row'}->taxnum; + my $url = "${p}edit/cust_main_county-expand.cgi?$taxnum"; + + '<FONT SIZE="-1">'. + include( '/elements/popup_link.html', + 'label' => $param{'label'}, + 'action' => $url, + 'actionlabel' => $param{'desc'}, + 'height' => 420, + #default# 'width' => 540, + #default# 'color' => '#333399', + ). + '</FONT>'; +} + +sub separate_taxclasses_link { + my( $row ) = @_; + my $taxnum = $row->taxnum; + my $url = "${p}edit/process/cust_main_county-expand.cgi?taxclass=1;taxnum=$taxnum"; + + qq!<FONT SIZE="-1"><A HREF="$url">!; +} + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +#my $conf = new FS::Conf; +#my $money_char = $conf->config('money_char') || '$'; +my $enable_taxclasses = $conf->exists('enable_taxclasses'); + +my @menubar; + +my $html_init = + "Click on <u>add states</u> to specify a country's tax rates by state or province. + <BR>Click on <u>add counties</u> to specify a state's tax rates by county."; +$html_init .= "<BR>Click on <u>separate taxclasses</u> to specify taxes per taxclass." + if $enable_taxclasses; +$html_init .= '<BR><BR>'; + +$html_init .= include('/elements/init_overlib.html'); + +my $title = ''; + +my $country = ''; +if ( $cgi->param('country') =~ /^(\w\w)$/ ) { + $country = $1; + $title = $country; +} +$cgi->delete('country'); + +my $state = ''; +if ( $country && $cgi->param('state') =~ /^([\w \-\'\[\]]+)$/ ) { + $state = $1; + $title = "$state, $title"; +} +$cgi->delete('state'); + +my $county = ''; +if ( $country && $state && + $cgi->param('county') =~ + /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/ + ) +{ + $county = $1; + if ( $county eq '__NONE__' ) { + $title = "No county, $title"; + } else { + $title = "$county county, $title"; + } +} +$cgi->delete('county'); + +$title = " for $title" if $title; + +my $taxclass = ''; +if ( $cgi->param('taxclass') =~ /^([\w \-]+)$/ ) { + $taxclass = $1; + $title .= " for $taxclass tax class"; +} +$cgi->delete('taxclass'); + +if ( $country || $taxclass ) { + push @menubar, 'View all tax rates' => $p.'browse/cust_main_county.cgi'; +} + +$cgi->param('dummy', 1); + +my $filter_change = + "window.location = '". $cgi->self_url. + ";country=' + encodeURIComponent( document.getElementById('country').options[document.getElementById('country').selectedIndex].value ) + ". + "';state=' + encodeURIComponent( document.getElementById('state').options[document.getElementById('state').selectedIndex].value ) +". + "';county=' + encodeURIComponent( document.getElementById('county').options[document.getElementById('county').selectedIndex].value );"; + +#restore this so pagination works +$cgi->param('country', $country) if $country; +$cgi->param('state', $state ) if $state; +$cgi->param('county', $county ) if $county; +$cgi->param('taxclass', $county ) if $taxclass; + +my $html_posttotal = + '<BR>( show country: '. + include('/elements/select-country.html', + 'country' => $country, + 'onchange' => $filter_change, + 'empty_label' => '(all)', + 'disable_empty' => 0, + 'disable_stateupdate' => 1, + ); + +my %states_hash = $country ? states_hash($country) : (); +if ( scalar(keys(%states_hash)) > 1 ) { + $html_posttotal .= + ' show state: '. + include('/elements/select-state.html', + 'country' => $country, + 'state' => $state, + 'onchange' => $filter_change, + 'empty_label' => '(all)', + 'disable_empty' => 0, + 'disable_countyupdate' => 1, + ); +} else { + $html_posttotal .= + '<SELECT NAME="state" ID="state" STYLE="display:none">'. + ' <OPTION VALUE="" SELECTED>'. + '</SELECT>'; +} + +my @counties = ( $country && $state ) ? counties($state, $country) : (); +if ( scalar(@counties) > 1 ) { + $html_posttotal .= + ' show county: '. + include('/elements/select-county.html', + 'country' => $country, + 'state' => $state, + 'county' => $county, + 'onchange' => $filter_change, + 'empty_label' => '(all)', + 'empty_data_label' => '(none)', + 'empty_data_value' => '__NONE__', + 'disable_empty' => 0, + 'disable_countyupdate' => 1, + ); +} else { + $html_posttotal .= + '<SELECT NAME="county" ID="county" STYLE="display:none">'. + ' <OPTION VALUE="" SELECTED>'. + '</SELECT>'; +} + +$html_posttotal .= ' )'; + +my $bulk_popup_link = + include( '/elements/popup_link_onclick.html', + 'action' => "${p}edit/bulk-cust_main_county.html?MAGIC_taxnum_MAGIC", + 'actionlabel' => 'Bulk add new tax', + 'nofalse' => 1, + 'height' => 420, + #default# 'width' => 540, + #default# 'color' => '#333399', + ); + +my $html_foot = <<END; +<SCRIPT TYPE="text/javascript"> + + function setAll(setTo) { + theForm = document.taxesForm; + for (i=0,n=theForm.elements.length;i<n;i++) { + if (theForm.elements[i].name.indexOf("cust_main_county") != -1) { + theForm.elements[i].checked = setTo; + } + } + } + + function toggleAll() { + theForm = document.taxesForm; + for (i=0,n=theForm.elements.length;i<n;i++) { + if (theForm.elements[i].name.indexOf("cust_main_county") != -1) { + if ( theForm.elements[i].checked == true ) { + theForm.elements[i].checked = false; + } else { + theForm.elements[i].checked = true; + } + } + } + } + + function bulkPopup() { + var bulk_popup_link = "$bulk_popup_link"; + var bulkstring = ''; + theForm = document.taxesForm; + for (i=0,n=theForm.elements.length;i<n;i++) { + if ( theForm.elements[i].name.indexOf("cust_main_county") != -1 + && theForm.elements[i].checked == true + ) { + var name = theForm.elements[i].name; + var taxnum = name.replace(/cust_main_county/, ''); + if ( bulkstring != '' ) { + bulkstring = bulkstring + ','; + } + bulkstring = bulkstring + taxnum; + + } + } + if ( bulk_popup_link.length > 1920 ) { // IE 2083 URL limit + alert('Too many selections'); // should do some session thing... + return false; + } + bulk_popup_link = bulk_popup_link.replace(/MAGIC_taxnum_MAGIC/, bulkstring); + eval(bulk_popup_link); + } + +</SCRIPT> + +<BR> +<A HREF="javascript:setAll(true)">select all</A> | +<A HREF="javascript:setAll(false)">unselect all</A> | +<A HREF="javascript:toggleAll()">toggle all</A> +<BR><BR> +<A HREF="javascript:void(0);" onClick="bulkPopup();">Add new tax to selected</A> + +END + +my $hashref = {}; +my $count_query = 'SELECT COUNT(*) FROM cust_main_county'; +if ( $country ) { + $hashref->{'country'} = $country; + $count_query .= ' WHERE country = '. dbh->quote($country); +} +if ( $state ) { + $hashref->{'state'} = $state; + $count_query .= ' AND state = '. dbh->quote($state); +} +if ( $county ) { + if ( $county eq '__NONE__' ) { + $hashref->{'county'} = ''; + $count_query .= " AND ( county = '' OR county IS NULL ) "; + } else { + $hashref->{'county'} = $county; + $count_query .= ' AND county = '. dbh->quote($county); + } +} +if ( $taxclass ) { + $hashref->{'taxclass'} = $taxclass; + $count_query .= ( $count_query =~ /WHERE/i ? ' AND ' : ' WHERE ' ). + ' taxclass = '. dbh->quote($taxclass); +} + + +$cell_style = ''; + +my @header = ( 'Country', 'State/Province', 'County',); +my @header2 = ( '', '', '', ); +my @links = ( '', '', '', ); +my @link_onclicks = ( '', '', '', ); +my $align = 'lll'; + +my @fields = ( + sub { my $country = shift->country; + code2country($country). " ($country)"; + }, + sub { state_label($_[0]->state, $_[0]->country). + ( $_[0]->state + ? '' + : ' '. expand_link( desc => 'Add States', + row => $_[0], + label => 'add states', + ) + ) + }, + sub { $_[0]->county || '(all) '. + expand_link( desc => 'Add Counties', + row => $_[0], + label => 'add counties', + ) + }, +); + +my @color = ( + '000000', + sub { shift->state ? '000000' : '999999' }, + sub { shift->county ? '000000' : '999999' }, +); + +if ( $conf->exists('enable_taxclasses') ) { + push @header, qq!Tax class (<A HREF="${p}edit/part_pkg_taxclass.html">add new</A>)!; + push @header2, '(per-package classification)'; + push @fields, sub { $_[0]->taxclass || '(all) '. + separate_taxclasses_link($_[0], 'Separate Taxclasses'). + 'separate taxclasses</A></FONT>' + }; + push @color, sub { shift->taxclass ? '000000' : '999999' }; + push @links, ''; + push @link_onclicks, ''; + $align .= 'l'; +} + +push @header, + '', #checkbox column + 'Tax name', + 'Rate', #'Tax', + 'Exemptions', + ; + +push @header2, + '', + '(printed on invoices)', + '', + '', + ; + +my $newregion = 1; +my $cb_oldrow = ''; +my $cb_sub = sub { + my $cust_main_county = shift; + + if ( $cb_oldrow ) { + if ( $cb_oldrow->country ne $cust_main_county->country + || $cb_oldrow->state ne $cust_main_county->state + || $cb_oldrow->county ne $cust_main_county->county + || $cb_oldrow->taxclass ne $cust_main_county->taxclass ) + { + $newregion = 1; + } else { + $newregion = 0; + } + + } else { + $newregion = 1; + } + $cb_oldrow = $cust_main_county; + + if ( $newregion ) { + my $taxnum = $cust_main_county->taxnum; + qq!<INPUT NAME="cust_main_county$taxnum" TYPE="checkbox" VALUE="1">!; + } else { + ''; + } +}; + +push @fields, + $cb_sub, + sub { shift->taxname || 'Tax' }, + sub { shift->tax. '% <FONT SIZE="-1">(edit)</FONT>' }, + $exempt_sub, +; + +push @color, + '000000', + sub { shift->taxname ? '000000' : '666666' }, + sub { shift->tax ? '000000' : '666666' }, + '000000', +; + +$align .= 'clrl'; + +my @cell_style = map $cell_style_sub, (1..scalar(@header)); + +push @links, '', '', $edit_link, ''; +push @link_onclicks, '', '', $edit_onclick, ''; + +</%init> diff --git a/httemplate/browse/elements/browse.html b/httemplate/browse/elements/browse.html new file mode 100644 index 000000000..513c2c4e9 --- /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..8ce131ac2 --- /dev/null +++ b/httemplate/browse/inventory_class.html @@ -0,0 +1,93 @@ +<% 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, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +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' ]; + +</%init> diff --git a/httemplate/browse/invoice_template.html b/httemplate/browse/invoice_template.html new file mode 100644 index 000000000..0bbfb2452 --- /dev/null +++ b/httemplate/browse/invoice_template.html @@ -0,0 +1,124 @@ +<% include("/elements/header.html", 'Invoice templates') %> + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Template</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">HTML</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Print/PDF (typeset)</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Plaintext</TH> +</TR> + +% foreach my $templatename ( '', @templatenames ) { +% my $tname = length($templatename) ? "_$templatename" : ''; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $display = length($templatename) ? $templatename : '<i>(Default)</i>'; + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $display %> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + +% my( $logo_label, $logo_link_label)= length( $templatename ) +% ? labels("logo_$templatename.png") +% : ( '', 'edit' ); + <% $logo_label %> Logo + (<A HREF="<% $p %>edit/invoice_logo.html?type=png;name=<% $templatename %>"><% $logo_link_label %></A>) + <BR> + +% foreach my $suffix (qw( returnaddress notes footer), '' ) { +% my $file = "invoice_html$suffix$tname"; +% my($label, $link_label) = length($templatename) +% ? labels($file) +% : ( '', 'edit' ); + + <% $label %> <% $suffix2name{$suffix} %> + (<A HREF="<% $p %>edit/invoice_template.html?type=html;suffix=<% $suffix %>;name=<% $templatename %>"><% $link_label %></A>) + <BR> + +% } + + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + +% my( $logo_label, $logo_link_label)= length( $templatename ) +% ? labels("logo_$templatename.eps") +% : ( '', 'edit' ); + <% $logo_label %> Logo + (<A HREF="<% $p %>edit/invoice_logo.html?type=eps;name=<% $templatename %>"><% $logo_link_label %></A>) + <BR> + +% foreach my $suffix (qw( returnaddress notes footer smallfooter), '' ) { +% my $file = "invoice_latex$suffix$tname"; +% my($label, $link_label) = length($templatename) +% ? labels($file) +% : ( '', 'edit' ); + + <% $label %> <% $suffix2name{$suffix} %> + (<A HREF="<% $p %>edit/invoice_template.html?type=latex;suffix=<% $suffix %>;name=<% $templatename %>"><% $link_label %></A>) + <BR> + +% } + + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + +% my( $txt_label, $txtlink_label)= +% length( $templatename ) +% ? labels("invoice_template_$templatename.png") +% : ( 'Main template', 'edit' ); + <% $txt_label %> + (<A HREF="<% $p %>edit/invoice_template.html?type=text;name=<% $templatename %>"><% $txtlink_label %></A>) + + </TD> + + </TR> + +% } + +<% include("/elements/footer.html") %> + +<%once> + +my %suffix2name = ( + 'returnaddress' => 'Return address', + 'notes' => 'Notes', + 'footer' => 'Footer', + 'smallfooter' => 'Small footer', + '' => 'Main template', +); + +my $conf = new FS::Conf; + +sub labels { + my $filename = shift; + if ( $conf->exists($filename) ) { + ( 'Custom', 'edit' ); + } else { + ( 'Standard', 'customize' ); + } +} + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my @templatenames = $conf->invoice_templatenames; + +</%init> diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi new file mode 100755 index 000000000..2c916dc9f --- /dev/null +++ b/httemplate/browse/msgcat.cgi @@ -0,0 +1,44 @@ +<% include('/elements/header.html', "View Message catalog", menubar( + 'Edit message catalog' => $p. "edit/msgcat.cgi", +)) %> +<% $widget->html %> +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +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; + }, + +); + +</%init> diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi new file mode 100755 index 000000000..b5e0ef8b7 --- /dev/null +++ b/httemplate/browse/nas.cgi @@ -0,0 +1,82 @@ +%print header('NAS ports'); +% +%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</ +% + +<%init> + +#this hasn't been used in ages, and isn't linked from anywhere... +die 'NAS browse not currently active'; + +</%init> diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi new file mode 100755 index 000000000..11bc14e5c --- /dev/null +++ b/httemplate/browse/part_bill_event.cgi @@ -0,0 +1,122 @@ +<% include('/elements/header.html', 'Invoice Event Listing') %> + + <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken on open invoices. Any events still listed here should be migrated to new-style events.</FONT><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'; +% my $reason = $part_bill_event->reasontext ; +% +% 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">Reason</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 %>"> + <% $reason %></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> +% } +% } + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +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); + +</%init> diff --git a/httemplate/browse/part_event.html b/httemplate/browse/part_event.html new file mode 100644 index 000000000..674004bc7 --- /dev/null +++ b/httemplate/browse/part_event.html @@ -0,0 +1,167 @@ +<% include( 'elements/browse.html', + 'title' => 'Billing Event Definitions', + 'html_init' => $html_init, + 'name' => 'billing event definitions', + 'disableable' => 1, + 'disabled_statuspos' => 2, + 'agent_virt' => 1, + 'agent_null_right' => 'Edit global billing events', + 'agent_pos' => 3, + 'query' => { 'select' => 'part_event.*', + 'table' => 'part_event', + 'addl_from' => $join_conditions, + 'hashref' => {}, + 'order_by' => $order_conditions, + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Event', + 'Type', + 'Check freq.', + 'Conditions', + 'Action', + ], + 'fields' => [ 'eventpart', + 'event', + $eventtable_sub, + $check_freq_sub, + $conditions_sub, + $action_sub, + ], + 'links' => [ $link, + $link, + '', + '', + '', + '', + ], + 'align' => 'rllccc', + ) +%> +<%once> + +my $eventtable_labels = FS::part_event->eventtable_labels; +my $eventtable_sub = sub { $eventtable_labels->{ shift->eventtable }; }; + +my $check_freq_labels = FS::part_event->check_freq_labels; +my $check_freq_sub = sub { $check_freq_labels->{ shift->check_freq }; }; + +my $conditions_sub = sub { + my $part_event = shift; + my $addl = 0; + + [ + map { + my $part_event_condition = $_; + my %options = $part_event_condition->options; + + [ + { + 'data' => $part_event_condition->description, + 'width' => '100%', + 'align' => 'center', + 'colspan' => 2, + 'style' => ( $addl++ ? 'border-top: 1px solid gray' : '' ), + }, + ], + + map { + + my $data = $options{$_}; + if ( ref($data) ) { + $data = join('<BR>', keys %$data); #XXX display hash values too? + } + + [ + { + 'data' => $part_event_condition->option_label($_). ':', + 'align' => 'right', + 'valign' => 'top', + 'size' => '-1', + }, + { + 'data' => $data, + 'align' => 'left', + 'size' => '-1', + }, + ]; + + } keys %options + + } + $part_event->part_event_condition + + ]; + +}; + +my $action_sub = sub { + my $part_event = shift; + + my %options = $part_event->options; + + [ + + [ + { + 'data' => $part_event->description, + 'width' => '100%', + 'align' => 'center', + 'colspan' => 2, + }, + ], + + map { + [ + { + 'data' => $part_event->option_label($_). ':', + 'align' => 'right', + 'size' => '-1', + }, + { + 'data' => $options{$_}, + 'align' => 'left', + 'size' => '-1', + }, + ]; + } + + keys %options + ]; + +}; + +my $link = [ $p.'edit/part_event.html?', 'eventpart' ]; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Edit billing events') + || $FS::CurrentUser::CurrentUser->access_right('Edit global billing events'); + +my $html_init = + #XXX better description + 'Events are billing, collection or other actions triggered when certain '. + 'customer, invoice, package or other conditions are met.<BR><BR>'. + qq!<FORM METHOD="POST" ACTION="${p}edit/part_event.html">!. + qq!<A HREF="${p}edit/part_event.html"><I>Add a new event</I></A>!. + ' or <SELECT NAME="clone"><OPTION></OPTION>'; + +foreach my $part_event ( qsearch('part_event', {'diabled'=>''}) ) { + $html_init .= '<OPTION VALUE="'. $part_event->eventpart. '">'. + $part_event->event. '</OPTION>'; +} + +$html_init .= '</SELECT><INPUT TYPE="submit" VALUE="Clone existing event">'. + '</FORM><BR>'; + +my $count_query = 'SELECT COUNT(*) FROM part_event WHERE '. + $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'Edit global billing events', + ); + +my $join_conditions = FS::part_event_condition->join_conditions_sql; +my $order_conditions = FS::part_event_condition->order_conditions_sql; + +</%init> diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi new file mode 100755 index 000000000..1cd201360 --- /dev/null +++ b/httemplate/browse/part_export.cgi @@ -0,0 +1,65 @@ +<% include("/elements/header.html", "Export Listing") %> + +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> + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + + <TR> + <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">Export</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Options</TH> + </TR> + +% foreach my $part_export ( sort { +% $a->getfield('exportnum') <=> $b->getfield('exportnum') +% } qsearch('part_export',{}) +% ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $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 CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <% itable() %> +% my %opt = $part_export->options; +% foreach my $opt ( keys %opt ) { + + <TR> + <TD ALIGN="right" VALIGN="top" WIDTH="33%"><% $opt %>: </TD> + <TD ALIGN="left" WIDTH="67%"><% encode_entities($opt{$opt}) %></TD> + </TR> +% } + + </TABLE> + </TD> + + </TR> + +% } + +</TABLE> + +<% include('/elements/footer.html') %> + +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +</%init> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi new file mode 100755 index 000000000..801c09f8f --- /dev/null +++ b/httemplate/browse/part_pkg.cgi @@ -0,0 +1,367 @@ +<% include( 'elements/browse.html', + 'title' => 'Package Definitions', + 'html_init' => $html_init, + 'name' => 'package definitions', + 'disableable' => 1, + 'disabled_statuspos' => 3, + 'agent_virt' => 1, + 'agent_null_right' => [ $edit, $edit_global ], + 'agent_null_right_link' => $edit_global, + 'agent_pos' => 5, + 'query' => { 'select' => $select, + 'table' => 'part_pkg', + 'hashref' => {}, + 'extra_sql' => $extra_sql, + 'order_by' => "ORDER BY $orderby" + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'align' => $align, + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +my $edit = 'Edit package definitions'; +my $edit_global = 'Edit global package definitions'; +my $acl_edit = $curuser->access_right($edit); +my $acl_edit_global = $curuser->access_right($edit_global); +my $acl_config = $curuser->access_right('Configuration'); #to edit services + #and agent types + +die "access denied" + unless $acl_edit || $acl_edit_global; + +my $conf = new FS::Conf; +my $taxclasses = $conf->exists('enable_taxclasses'); +my $money_char = $conf->config('money_char') || '$'; + +my $select = '*'; +my $orderby = 'pkgpart'; +if ( $cgi->param('active') ) { + $orderby = 'num_active DESC'; +} + +my $extra_sql = ''; + +unless ( $acl_edit_global ) { + $extra_sql .= ' WHERE '. FS::part_pkg->curuser_pkgs_sql; +} + +my $agentnums = join(',', $curuser->agentnums); +my $count_cust_pkg = " + SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum ) + WHERE cust_pkg.pkgpart = part_pkg.pkgpart + AND cust_main.agentnum IN ($agentnums) +"; + +$select = " + + *, + + ( $count_cust_pkg + AND ( cancel IS NULL OR cancel = 0 ) + AND ( susp IS NULL OR susp = 0 ) + ) AS num_active, + + ( $count_cust_pkg + AND ( cancel IS NULL OR cancel = 0 ) + AND susp IS NOT NULL AND susp != 0 + ) AS num_suspended, + + ( $count_cust_pkg + AND cancel IS NOT NULL AND cancel != 0 + ) AS num_cancelled + +"; + +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> + <FORM METHOD="POST" ACTION="${p}edit/part_pkg.cgi"> + <A HREF="${p}edit/part_pkg.cgi"><I>Add a new package definition</I></A> + or + !.include('/elements/select-part_pkg.html', 'element_name' => 'clone' ). qq! + <INPUT TYPE="submit" VALUE="Clone existing package"> + </FORM> + <BR><BR> + !; +#} + +# ------ + +my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ]; + +my @header = ( '#', 'Package', 'Comment' ); +my @fields = ( 'pkgpart', 'pkg', 'comment' ); +my $align = 'rll'; +my @links = ( $link, $link, '' ); + +unless ( 0 ) { #already showing only one class or something? + push @header, 'Class'; + push @fields, sub { shift->classname || '(none)'; }; + $align .= 'l'; +} + +tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() }; + +tie my %plan_labels, 'Tie::IxHash', + map { $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) } + keys %plans; + +push @header, 'Pricing'; +$align .= 'r'; #? +push @fields, sub { + my $part_pkg = shift; + (my $plan = $plan_labels{$part_pkg->plan} ) =~ s/ / /g; + my $is_recur = ( $part_pkg->freq ne '0' ); + + [ + [ + { data =>$plan, + align=>'center', + colspan=>2, + }, + ], + [ + { data =>$money_char. + sprintf('%.2f', $part_pkg->option('setup_fee') ), + align=>'right' + }, + { data => ( $is_recur ? ' setup' : ' one-time' ), + align=>'left', + }, + ], + [ + { data=>( $is_recur + ? $money_char.sprintf('%.2f ', $part_pkg->option('recur_fee') ) + : $part_pkg->freq_pretty + ), + align=> ( $is_recur ? 'right' : 'center' ), + colspan=> ( $is_recur ? 1 : 2 ), + }, + ( $is_recur + ? { data => ( $is_recur ? $part_pkg->freq_pretty : '' ), + align=>'left', + } + : () + ), + ], + ( map { + my $dst_pkg = $_->dst_pkg; + [ + { data => 'Add-on: '.$dst_pkg->pkg_comment, + align=>'center', #? + colspan=>2, + } + ] + } + $part_pkg->bill_part_pkg_link + ), + ]; + +# $plan_labels{$part_pkg->plan}.'<BR>'. +# $money_char.sprintf('%.2f setup<BR>', $part_pkg->option('setup_fee') ). +# ( $part_pkg->freq ne '0' +# ? $money_char.sprintf('%.2f ', $part_pkg->option('recur_fee') ) +# : '' +# ). +# $part_pkg->freq_pretty; #.'<BR>' +}; + +### +# Agent goes here if displayed +### + +#agent type +if ( $acl_edit_global ) { + #really we just want a count, but this is fine unless someone has tons + my @all_agent_types = map {$_->typenum} qsearch('agent_type',{}); + if ( scalar(@all_agent_types) > 1 ) { + push @header, 'Agent types'; + my $typelink = $p. 'edit/agent_type.cgi?'; + push @fields, sub { my $part_pkg = shift; + [ + map { warn $_; + my $agent_type = $_->agent_type; + warn $agent_type; + [ + { 'data' => $agent_type->atype, #escape? + 'align' => 'left', + 'link' => ( $acl_config + ? $typelink. + $agent_type->typenum + : '' + ), + }, + ]; + } + $part_pkg->type_pkgs + ]; + }; + $align .= 'l'; + } +} + +#if ( $cgi->param('active') ) { + push @header, 'Customer<BR>packages'; + my %col = ( + 'active' => '00CC00', + 'suspended' => 'FF9900', + 'cancelled' => 'FF0000', + #'one-time charge' => '000000', + 'charge' => '000000', + ); + my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart='; + push @fields, sub { my $part_pkg = shift; + [ + map { + my $magic = $_; + my $label = $_; + if ( $magic eq 'active' && $part_pkg->freq == 0 ) { + $magic = 'inactive'; + #$label = 'one-time charge', + $label = 'charge', + } + + [ + { + 'data' => '<B><FONT COLOR="#'. $col{$label}. '">'. + $part_pkg->get("num_$_"). + '</FONT></B>', + 'align' => 'right', + }, + { + 'data' => $label. + ( $part_pkg->get("num_$_") != 1 + && $label =~ /charge$/ + ? 's' + : '' + ), + 'align' => 'left', + 'link' => ( $part_pkg->get("num_$_") + ? $cust_pkg_link. + $part_pkg->pkgpart. + ";magic=$magic" + : '' + ), + }, + ], + } (qw( active suspended cancelled )) + ]; }; + $align .= 'r'; +#} + +if ( $taxclasses ) { + push @header, 'Taxclass'; + push @fields, sub { shift->taxclass() || ' '; }; + $align .= 'l'; +} + +push @header, 'Plan options', + 'Services'; + #'Service', 'Quan', 'Primary'; + +push @fields, + sub { + my $part_pkg = shift; + if ( $part_pkg->plan ) { + + my %options = $part_pkg->options; + + [ map { + [ + { 'data' => $_, + 'align' => 'right', + }, + { 'data' => $part_pkg->format($_,$options{$_}), + 'align' => 'left', + }, + ]; + } + grep { $options{$_} =~ /\S/ } + grep { $_ !~ /^(setup|recur)_fee$/ } + keys %options + ]; + + } 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' => ( $acl_config + ? $p. 'edit/part_svc.cgi?'. + $part_svc->svcpart + : '' + ), + }, + ]; + } + sort { $b->primary_svc =~ /^Y/i + <=> $a->primary_svc =~ /^Y/i + } + $part_pkg->pkg_svc('disable_linked'=>1) + ), + ( map { + my $dst_pkg = $_->dst_pkg; + [ + { data => 'Add-on: '.$dst_pkg->pkg_comment, + align=>'center', #? + colspan=>2, + } + ] + } + $part_pkg->svc_part_pkg_link + ) + ]; + + }; + +$align .= 'lrl'; #rr'; + +# -------- + +my $count_query = "SELECT COUNT(*) FROM part_pkg $extra_sql"; + +</%init> diff --git a/httemplate/browse/part_pkg_taxproduct.cgi b/httemplate/browse/part_pkg_taxproduct.cgi new file mode 100755 index 000000000..7e0cb8191 --- /dev/null +++ b/httemplate/browse/part_pkg_taxproduct.cgi @@ -0,0 +1,263 @@ +<% include( 'elements/browse.html', + 'title' => "Tax Products $title", + 'name_singular' => 'tax product', + 'menubar' => \@menubar, + 'html_init' => $html_init, + 'query' => { + 'table' => 'part_pkg_taxproduct', + 'hashref' => $hashref, + 'order_by' => 'ORDER BY description', + 'extra_sql' => $extra_sql, + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'align' => $align, + 'links' => \@links, + 'link_onclicks' => \@link_onclicks, + ) +%> +<%once> + +my $conf = new FS::Conf; + +my $select_link = [ 'javascript:void(0);', sub { ''; } ]; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my @menubar; +my $title = ''; +my $onclick = 'cClick'; + +my $data_vendor = ''; +if ( $cgi->param('data_vendor') =~ /^(\w+)$/ ) { + $data_vendor = $1; + $title = "$data_vendor"; +} +$cgi->delete('data_vendor'); + +$title = " for $title" if $title; + +my $taxproductnum = $1 + if ( $cgi->param('taxproductnum') =~ /^(\d+)$/ ); +my $tax_group = $1 + if ( $cgi->param('tax_group') =~ /^([- \w\(\).\/]+)$/ ); +my $tax_item = $1 + if ( $cgi->param('tax_item') =~ /^([- \w\(\).\/&%]+)$/ ); +my $tax_provider = $1 + if ( $cgi->param('tax_provider') =~ /^([ \w]+)$/ ); +my $tax_customer = $1 + if ( $cgi->param('tax_customer') =~ /^([ \w]+)$/ ); +my $id = $1 + if ( $cgi->param('id') =~ /^([ \w]+)$/ ); + +$onclick = $1 + if ( $cgi->param('onclick') =~ /^(\w+)$/ ); +$cgi->delete('onclick'); + +my $remove_onclick = <<EOS + parent.document.getElementById('$id').value = ''; + parent.document.getElementById('${id}_description').value = ''; + parent.$onclick(); +EOS + if $id; + +my $select_onclick = sub { + my $row = shift; + my $taxnum = $row->taxproductnum; + my $desc = $row->description; + "parent.document.getElementById('$id').value = $taxnum;". + "parent.document.getElementById('${id}_description').value = '$desc';". + "parent.$onclick();"; +} + if $id; + +my $selected_part_pkg_taxproduct; +if ($taxproductnum) { + $selected_part_pkg_taxproduct = + qsearchs('part_pkg_taxproduct', { 'taxproductnum' => $taxproductnum }); +} + +my $hashref = {}; +my $extra_sql = ''; +if ( $data_vendor ) { + $extra_sql .= ' WHERE data_vendor = '. dbh->quote($data_vendor); +} + +if ($tax_group || $tax_item || $tax_customer || $tax_provider) { + my $compare = "LIKE '". ( $tax_group || "%" ). " : ". ( $tax_item || "%" ). " : ". + ( $tax_provider || "%" ). " : ". ( $tax_customer || "%" ). "'"; + $compare = "= '$tax_group:$tax_item:$tax_provider:$tax_customer'" + if ($tax_group && $tax_item && $tax_provider && $tax_customer); + + $extra_sql .= ($extra_sql =~ /WHERE/ ? ' AND ' : ' WHERE '). + "description $compare"; + +} +$cgi->delete('tax_group'); +$cgi->delete('tax_item'); +$cgi->delete('tax_provider'); +$cgi->delete('tax_customer'); + + +if ( $tax_group || $tax_item || $tax_provider || $tax_customer ) { + push @menubar, 'View all tax products' => $p.'browse/part_pkg_taxproduct.cgi'; +} + +$cgi->param('dummy', 1); + +#restore this so pagination works +$cgi->param('data_vendor', $data_vendor) if $data_vendor; +$cgi->param('tax_group', $tax_group) if $tax_group; +$cgi->param('tax_item', $tax_item ) if $tax_item; +$cgi->param('tax_provider', $tax_provider ) if $tax_provider; +$cgi->param('tax_customer', $tax_customer ) if $tax_customer; +$cgi->param('onclick', $onclick ) if $onclick; + +my $count_query = "SELECT COUNT(*) FROM part_pkg_taxproduct $extra_sql"; + +my @header = ( 'Data Vendor', 'Group', 'Item', 'Provider', 'Customer' ); +my @links = ( $select_link, + $select_link, + $select_link, + $select_link, + $select_link, + ); +my @link_onclicks = ( $select_onclick, + $select_onclick, + $select_onclick, + $select_onclick, + $select_onclick, + ); +my $align = 'lllll'; + +my @fields = ( + 'data_vendor', + sub { shift->description =~ /^(.*):.*:.*:.*$/; $1;}, + sub { shift->description =~ /^.*:(.*):.*:.*$/; $1;}, + sub { shift->description =~ /^.*:.*:(.*):.*$/; $1;}, + sub { shift->description =~ /^.*:.*:.*:(.*)$/; $1;}, +); + +my $html_init = ''; + +my $select_link = [ 'javascript:void(0);', sub { ''; } ]; +$html_init = '<TABLE><TR><TD><A HREF="javascript:void(0)" '. + qq!onClick="$remove_onclick">(remove)</A> !. + 'Current tax product: </TD><TD>'. + $selected_part_pkg_taxproduct->description. + '</TD></TR></TABLE><BR><BR>' + if $selected_part_pkg_taxproduct; + +my $type = $cgi->param('_type'); +$html_init .= qq( + <FORM> + <INPUT NAME="_type" TYPE="hidden" VALUE="$type"> + <INPUT NAME="taxproductnum" TYPE="hidden" VALUE="$taxproductnum"> + <INPUT NAME="onclick" TYPE="hidden" VALUE="$onclick"> + <INPUT NAME="id" TYPE="hidden" VALUE="$id"> + <TABLE> + <TR> + <TD><SELECT NAME="data_vendor" onChange="this.form.submit()"> +); + +my $sql = "SELECT DISTINCT data_vendor FROM part_pkg_taxproduct ORDER BY data_vendor"; +my $dbh = dbh; +my $sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (['(choose data vendor)'], @{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $data_vendor ? " SELECTED" : ""). + '">'. $_->[0]; +} +$html_init .= qq( + </SELECT> + +<!-- cch specific --> + <TD><SELECT NAME="tax_group" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + qq!substring(description from '#"%#" : % : % : %' for '#'),!. + qq!substring(description from '#"%#" : % : % : %' for '#')!. + "FROM part_pkg_taxproduct ORDER BY 1"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (['', '(choose group)'], @{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_group ? " SELECTED" : ""). + '">'. $_->[1]; +} + +$html_init .= qq( + </SELECT> + + <TD><SELECT NAME="tax_item" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + qq!substring(description from '% : #"%#" : %: %' for '#'),!. + qq!substring(description from '% : #"%#" : %: %' for '#')!. + "FROM part_pkg_taxproduct ORDER BY 1"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (@{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_item ? " SELECTED" : ""). + '">'. ($_->[0] ? $_->[1] : '(choose item)'); +} + +$html_init .= qq( + </SELECT> + + <TD><SELECT NAME="tax_provider" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + qq!substring(description from '% : % : #"%#" : %' for '#'),!. + qq!substring(description from '% : % : #"%#" : %' for '#')!. + "FROM part_pkg_taxproduct ORDER BY 1"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (@{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_provider ? " SELECTED" : ""). + '">'. ($_->[0] ? $_->[1] : '(choose provider type)'); +} + +$html_init .= qq( + </SELECT> + + <TD><SELECT NAME="tax_customer" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + qq!substring(description from '% : % : % : #"%#"' for '#'),!. + qq!substring(description from '% : % : % : #"%#"' for '#')!. + "FROM part_pkg_taxproduct ORDER BY 1"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (@{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_customer ? " SELECTED" : ""). + '">'. ($_->[0] ? $_->[1] : '(choose customer type)'); +} + +$html_init .= qq( + </SELECT> + + </TR> + </TABLE> + </FORM> + +); + +</%init> diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html new file mode 100755 index 000000000..9cc32c459 --- /dev/null +++ b/httemplate/browse/part_referral.html @@ -0,0 +1,181 @@ +<% 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> + +<% 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 and Packages</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 ) { +% my @param = ( $part_referral->refnum, +% $today-$after{$period}, +% $today+$before{$period}, +% ); +% $cust_sth->execute(@param) or die $cust_sth->errstr; +% my $num_cust = $cust_sth->fetchrow_arrayref->[0]; +% $pkg_sth->execute(@param) or die $pkg_sth->errstr; +% my $num_pkg = $pkg_sth->fetchrow_arrayref->[0]; + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + <TR> + <TD ALIGN="right"><B><% $num_cust %></B></TD> + <TD ALIGN="left">customers</TD> + </TR> + <TR> + <TD ALIGN="right"><B><% $num_pkg %></B></TD> + <TD ALIGN="left">packages</TD> + </TR> + </TABLE> + </TD> +% } + + </TR> +% } +% +% $cust_statement =~ s/AND refnum = \?//; +% $cust_sth = dbh->prepare($cust_statement) +% or die dbh->errstr; +% $pkg_statement =~ s/AND h_pkg_referral\.refnum = \?//; +% $pkg_sth = dbh->prepare($pkg_statement) +% or die dbh->errstr; + + <TR> + <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD> +% for my $period ( keys %after ) { +% my @param = ( $today-$after{$period}, +% $today+$before{$period}, +% ); +% $cust_sth->execute( @param ) or die $cust_sth->errstr; +% my $num_cust = $cust_sth->fetchrow_arrayref->[0]; +% $pkg_sth->execute(@param) or die $pkg_sth->errstr; +% my $num_pkg = $pkg_sth->fetchrow_arrayref->[0]; + + <TD CLASS="inv" BGCOLOR="#dddddd" ALIGN="right"> + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> + <TR> + <TD ALIGN="right"><B><% $num_cust %></B></TD> + <TD ALIGN="left">customers</TD> + </TR> + <TR> + <TD ALIGN="right"><B><% $num_pkg %></B></TD> + <TD ALIGN="left">packages</TD> + </TR> + </TABLE> + </TD> + +% } + + </TR> + </TABLE> + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources') + || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources'); + +my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); + +tie my %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 $show_agentnums = ( scalar($curuser->agentnums) > 1 ); + +my $cust_statement = "SELECT COUNT(*) FROM h_cust_main + WHERE history_action = 'insert' + AND refnum = ? + AND history_date >= ? + AND history_date < ? + AND ". $curuser->agentnums_sql; +my $cust_sth = dbh->prepare($cust_statement) + or die dbh->errstr; + +my $pkg_statement = "SELECT COUNT(*) FROM h_pkg_referral + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN cust_main USING ( custnum ) + WHERE history_action = 'insert' + AND h_pkg_referral.refnum = ? + AND history_date >= ? + AND history_date < ? + AND ". $curuser->agentnums_sql; +my $pkg_sth = dbh->prepare($pkg_statement) + or die dbh->errstr; + +</%init> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi new file mode 100755 index 000000000..f1b283638 --- /dev/null +++ b/httemplate/browse/part_svc.cgi @@ -0,0 +1,215 @@ +<% include('/elements/header.html', 'Service Definition Listing') %> + +<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') ) ); + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + + <TR> + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svcpart'); $cgi->self_url } %>">#</A></TH> + +% if ( $cgi->param('showdisabled') ) { + <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> +% } + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'svc'); $cgi->self_url; } %>">Service</A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Table</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc"><A HREF="<% do { $cgi->param('orderby', 'active'); $cgi->self_url; } %>"><FONT SIZE=-1>Customer<BR>Services</FONT></A></TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Export</TH> + + <TH CLASS="grid" BGCOLOR="#cccccc">Field</TH> + + <TH COLSPAN=2 CLASS="grid" BGCOLOR="#cccccc">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; +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + + <TR> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $url %>"><% $part_svc->svcpart %></A> + </TD> + +% if ( $cgi->param('showdisabled') ) { + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $part_svc->disabled + ? '<FONT COLOR="#FF0000"><B>Disabled</B></FONT>' + : '<FONT COLOR="#00CC00"><B>Enabled</B></FONT>' + %> + </TD> +% } + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> + <% $part_svc->svc %></A></TD> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $svcdb %></TD> + + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <% $num_active_cust_svc{$part_svc->svcpart} ? svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart ) : '<A NAME="zero">' %>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 %> CLASS="inv" BGCOLOR="<% $bgcolor %>"> + <TABLE CLASS="inv"> +% +%# 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> + +% unless ( @fields ) { +% for ( 1..3 ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD> +% } +% } +% +% my($n1)=''; +% foreach my $field ( @fields ) { +% my $formatter = +% FS::part_svc->svc_table_fields($svcdb)->{$field}->{format} +% || sub { shift }; +% my $flag = $part_svc->part_svc_column($field)->columnflag; +% + + <% $n1 %> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $flag{$flag} %></TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% my $value = &$formatter($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> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +#code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm +my %flag = ( + '' => '', + 'D' => 'Default', + 'F' => 'Fixed (unchangeable)', + 'S' => 'Selectable choice', + #'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 = (); + +</%init> diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi new file mode 100644 index 000000000..b18440036 --- /dev/null +++ b/httemplate/browse/part_virtual_field.cgi @@ -0,0 +1,42 @@ +<% include('/elements/header.html', 'Virtual field definitions') %> + +<% include('/elements/error.html') %> + +<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> +% } + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %pvfs; +my $block; +my $p2 = popurl(2); +my $dbtable; + +foreach (qsearch('part_virtual_field', {})) { + push @{ $pvfs{$_->dbtable} }, $_; +} + +</%init> diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html new file mode 100644 index 000000000..848c58a82 --- /dev/null +++ b/httemplate/browse/payment_gateway.html @@ -0,0 +1,94 @@ +<% include( 'elements/browse.html', + 'title' => 'Payment gateways', + 'menubar' => [ 'Agents' => $p.'browse/agent.cgi', ], + 'html_init' => $html_init, + 'name' => 'payment gateways', + 'disableable' => 1, + 'disabled_statuspos' => 1, + 'query' => { 'table' => 'payment_gateway', + 'hashref' => {}, + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Gateway', + 'Username', + 'Password', + 'Action', + 'Options', + ], + 'fields' => [ 'gatewaynum', + $gateway_sub, + 'gateway_username', + sub { ' - '; }, + 'gateway_action', + $options_sub, + ], + ) +%> + +</TABLE> + +<% include('/elements/footer.html') %> +<%once> + +my $html_init = qq! + <A HREF="${p}edit/payment_gateway.html"><I>Add a new payment gateway</I></A> + <BR><BR> + + <SCRIPT> + function areyousure(href) { + if (confirm("Are you sure you want to disable this payment gateway?") == true) + window.location.href = href; + } + </SCRIPT> + +!; + +my $gateway_sub = sub { + my($payment_gateway) = @_; + + my $gatewaynum = $payment_gateway->gatewaynum; + + my $html = $payment_gateway->gateway_module. ' '. qq! + <FONT SIZE="-1"> + <A HREF="${p}edit/payment_gateway.html?$gatewaynum">(edit)</A> + !; + + unless ( $payment_gateway->disabled ) { + $html .= qq! + <A HREF="javascript:areyousure('${p}misc/disable-payment_gateway.cgi?$gatewaynum')">(disable)</A> + !; + } + + $html .= '</FONT>'; + + $html; + +}; + +my $options_sub = sub { + my($payment_gateway) = @_; + + #should return a structure instead of this manual formatting... + + my $html = '<TABLE CELLSPACING=0 CELLPADDING=0>'; + + my %options = $payment_gateway->options; + foreach my $option ( keys %options ) { + $html .= '<TR><TH>'. $option. ':</TH>'. + '<TD>'. $options{$option}. '</TD></TR>'; + } + $html .= '</TABLE>'; + + $html; +}; + +my $count_query = 'SELECT COUNT(*) FROM payment_gateway'; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/browse/pkg_category.html b/httemplate/browse/pkg_category.html new file mode 100644 index 000000000..20bf1a8df --- /dev/null +++ b/httemplate/browse/pkg_category.html @@ -0,0 +1,33 @@ +<% include( 'elements/browse.html', + 'title' => 'Package categories', + 'html_init' => $html_init, + 'name' => 'package categories', + 'disableable' => 1, + 'disabled_statuspos' => 2, + 'query' => { 'table' => 'pkg_category', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY categorynum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Category' ], + 'fields' => [ 'categorynum', 'categoryname' ], + 'links' => [ $link, $link ], + ) +%> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + qq!<A HREF="${p}browse/pkg_class.html">Package classes</A><BR><BR>!. + 'Package categories define groups of package classes, for reporting and '. + 'convenience purposes.<BR><BR>'. + qq!<A HREF="${p}edit/pkg_category.html"><I>Add a package category</I></A><BR><BR>!; + +my $count_query = 'SELECT COUNT(*) FROM pkg_category'; + +my $link = [ $p.'edit/pkg_category.html?', 'categorynum' ]; + +</%init> diff --git a/httemplate/browse/pkg_class.html b/httemplate/browse/pkg_class.html new file mode 100644 index 000000000..75969dbe8 --- /dev/null +++ b/httemplate/browse/pkg_class.html @@ -0,0 +1,46 @@ +<% include( 'elements/browse.html', + 'title' => 'Package classes', + 'html_init' => $html_init, + 'name' => 'package classes', + 'disableable' => 1, + 'disabled_statuspos' => 2, + 'query' => { 'table' => 'pkg_class', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY classnum', + }, + 'count_query' => $count_query, + 'header' => $header, + 'fields' => $fields, + 'links' => $links, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + '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' ]; + +my $header = [ '#', 'Class' ]; +my $fields = [ 'classnum', 'classname' ]; +my $links = [ $link, $link ]; + +my $cat_query = 'SELECT COUNT(*) FROM pkg_class where categorynum IS NOT NULL'; +my $sth = dbh->prepare($cat_query) + or die "Error preparing $cat_query: ". dbh->errstr; +$sth->execute + or die "Error executing $cat_query: ". $sth->errstr; +if ($sth->fetchrow_arrayref->[0]) { + push @$header, 'Category'; + push @$fields, 'categoryname'; + push @$links, $link; +} + +</%init> diff --git a/httemplate/browse/rate.cgi b/httemplate/browse/rate.cgi new file mode 100644 index 000000000..02d670fbd --- /dev/null +++ b/httemplate/browse/rate.cgi @@ -0,0 +1,64 @@ +<% include( 'elements/browse.html', + 'title' => 'Rate plans', + 'menubar' => [ 'Regions and Prefixes' => + $p.'browse/rate_region.html', + ], + 'html_init' => $html_init, + 'name' => 'rate plans', + 'query' => { 'table' => 'rate', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY ratenum', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Rate plan', 'Rates' ], + 'fields' => [ 'ratenum', 'ratename', $rates_sub ], + 'links' => [ $link, $link, '' ], + ) +%> +<%once> + +my $all_countrycodes = join("\n", map qq(<OPTION VALUE="$_">$_), + FS::rate_prefix->all_countrycodes + ); + +my $rates_sub = sub { + my $rate = shift; + my $ratenum = $rate->ratenum; + + qq( <FORM METHOD="GET" ACTION="${p}browse/rate_detail.html"> + <INPUT TYPE="hidden" NAME="ratenum" VALUE="$ratenum"> + <SELECT NAME="countrycode" onChange="this.form.submit();"> + <OPTION SELECTED>Select Country Code + <OPTION VALUE="">(all) + $all_countrycodes + </SELECT> + </FORM> + ); + + +}; + +my $html_init = + 'Rate plans 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}misc/copy-rate_detail.html"><I>Copy rates between plans</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' ]; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/browse/rate_detail.html b/httemplate/browse/rate_detail.html new file mode 100644 index 000000000..23bc23ff8 --- /dev/null +++ b/httemplate/browse/rate_detail.html @@ -0,0 +1,92 @@ +<% include( 'elements/browse.html', + 'title' => $title, + 'name_singular' => 'rate', + 'html_init' => $html_init, + 'menubar' => [ 'Rate plans' => $p.'browse/rate.cgi' ], + 'query' => { + 'table' => 'rate_detail', + 'addl_from' => $join, + 'hashref' => { 'ratenum' => $ratenum }, + 'extra_sql' => $where, + }, + 'count_query' => "SELECT COUNT(*) FROM rate_detail $join". + " WHERE ratenum = $ratenum $where", + 'header' => [ + 'Region', + 'Prefix(es)', + 'Included<BR>minutes', + 'Charge per<BR>minute', + 'Granularity', + 'Usage class', + ], + 'fields' => [ + 'regionname', + sub { shift->dest_region->prefixes_short }, + sub { shift->min_included. + ' <FONT SIZE="-1">(edit)</FONT>'; + }, + sub { $money_char. shift->min_charge. + ' <FONT SIZE="-1">(edit)</FONT>'; + }, + sub { $granularity{ shift->sec_granularity } }, + 'classname', + ], + 'links' => [ '', '', $edit_link, $edit_link, '', '' ], + 'link_onclicks' => [ '', '', $edit_onclick, $edit_onclick, '', '' ], + 'align' => 'llrrcc', + ) +%> +<%once> + +tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $join = + ' JOIN rate_region ON ( rate_detail.dest_regionnum = rate_region.regionnum )'; + +my $edit_link = [ 'javascript:void(0);', sub { ''; } ]; + +my $edit_onclick = sub { + my $rate_detail = shift; + my $ratedetailnum = $rate_detail->ratedetailnum; + include( '/elements/popup_link_onclick.html', + 'action' => "${p}edit/rate_detail.html?$ratedetailnum", + 'actionlabel' => 'Edit rate', + 'height' => 420, + #default# 'width' => 540, + #default# 'color' => '#333399', + ); +}; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = include('/elements/init_overlib.html'); + +$cgi->param('ratenum') =~ /^(\d+)$/ or die "unparsable ratenum"; +my $ratenum = $1; +my $rate = qsearchs('rate', { 'ratenum' => $ratenum } ) + or die "unknown ratenum $ratenum"; +my $ratename = $rate->ratename; +my $title = "$ratename rates"; + +my @where = (); + +if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) { + my $countrycode = $1; + push @where, "0 < ( SELECT COUNT(*) FROM rate_prefix + WHERE rate_prefix.regionnum = rate_region.regionnum + AND countrycode = '$countrycode' + ) + "; + $title .= " for +$countrycode"; +} + +my $where = scalar(@where) ? ' AND '.join(' AND ', @where ) : ''; + +</%init> diff --git a/httemplate/browse/rate_region.html b/httemplate/browse/rate_region.html new file mode 100644 index 000000000..b454a9e74 --- /dev/null +++ b/httemplate/browse/rate_region.html @@ -0,0 +1,91 @@ +<% include( 'elements/browse.html', + 'title' => 'Rating Regions and Prefixes', + 'name_singular' => 'region', #'rate region', + 'menubar' => [ 'Rate plans' => $p.'browse/rate.cgi' ], + 'html_init' => $html_init, + 'html_posttotal' => $html_posttotal, + 'query' => { + 'select' => $select, + 'table' => 'rate_region', + 'addl_from' => $join, + 'extra_sql' => $extra_sql, + 'order_by' => 'ORDER BY LOWER(regionname)', + }, + 'count_query' => $count_query, + 'header' => [ '#', 'Region', 'Country code', 'Prefixes' ], + 'fields' => [ 'regionnum', 'regionname', 'ccode', 'prefixes' ], + 'links' => [ $link, $link, $link, $link ], + ) +%> +<%once> + +my $edit_url = $p.'edit/rate_region.cgi'; + +my $link = [ "$edit_url?", 'regionnum' ]; + +my $html_init = + 'Regions and prefixes for VoIP and call billing.<BR><BR>'. + qq(<A HREF="$edit_url"><I>Add a new region</I></A><BR><BR>); + +#not quite right for the shouldn't-happen multiple countrycode per region case +my $select = 'rate_region.*, '; +my $join = ''; +my $group_sql = ''; +if ( driver_name =~ /^Pg/ ) { + my $fromwhere = 'FROM rate_prefix'. + ' WHERE rate_prefix.regionnum = rate_region.regionnum'; + my $prefix_sql = " CASE WHEN nxx IS NULL OR nxx = '' ". + " THEN npa ". + " ELSE npa || '-' || nxx ". + " END"; + my $prefixes_sql = "SELECT $prefix_sql $fromwhere AND npa IS NOT NULL"; + $select .= "( SELECT countrycode $fromwhere LIMIT 1 ) AS ccode, + ARRAY_TO_STRING( ARRAY($prefixes_sql), ',' ) AS prefixes"; +} elsif ( driver_name =~ /^mysql/i ) { + $join = 'LEFT JOIN rate_prefix USING ( regionnum )'; + $select .= "GROUP_CONCAT( DISTINCT countrycode ) AS ccode, + GROUP_CONCAT( npa ORDER BY npa ) AS prefixes "; + $group_sql = 'GROUP BY regionnum, regionname'; +} else { + die 'unknown database '. driver_name; +} + +my $base_count_sql = 'SELECT COUNT(*) FROM rate_region'; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +$cgi->param('dummy', 1); +my $countrycode_filter_change = + "window.location = '". + $cgi->self_url. ";countrycode=' + this.options[this.selectedIndex].value;"; + +my $countrycode = ''; +my $extra_sql = $group_sql; +my $count_query = $base_count_sql; +if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) { + $countrycode = $1; + my $ccode_sql = '( SELECT countrycode FROM rate_prefix + WHERE rate_prefix.regionnum = rate_region.regionnum + LIMIT 1 + )'; + $extra_sql = " WHERE $ccode_sql = '$1' $extra_sql"; + $count_query .= " WHERE $ccode_sql = '$1'"; +} + +my $html_posttotal = + '(show country code: '. + qq(<SELECT NAME="countrycode" onChange="$countrycode_filter_change">). + qq(<OPTION VALUE="">(all)). + join("\n", map { qq(<OPTION VALUE="$_"). + ($_ eq $countrycode ? ' SELECTED' : '' ). + ">$_", + } + FS::rate_prefix->all_countrycodes + ). + '</SELECT>)'; + +</%init> diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html new file mode 100644 index 000000000..fe285be4a --- /dev/null +++ b/httemplate/browse/reason.html @@ -0,0 +1,53 @@ +<% include( 'elements/browse.html', + 'title' => ucfirst($classname) . ' Reasons', + 'menubar' => [ ucfirst($classname).' Reason Types' => + $p."browse/reason_type.html?class=$class" + ], + 'html_init' => $html_init, + 'name' => $classname . ' reasons', + 'disableable' => 1, + 'disabled_statuspos' => 3, + 'query' => { 'table' => 'reason', + 'hashref' => {}, + 'extra_sql' => $where_clause. + ' ORDER BY reason_type', + 'addl_from' => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type', + }, + 'count_query' => $count_query, + 'header' => [ '#', + ucfirst($classname) . ' Reason Type', + ucfirst($classname) . ' Reason', + ], + 'fields' => [ 'reasonnum', + sub { shift->reasontype->type }, + 'reason', + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +$cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +my $class = $1; + +my $classname = $FS::reason_type::class_name{$class}; +my $classpurpose = $FS::reason_type::class_purpose{$class}; + +my $html_init = ucfirst($classname). " reasons $classpurpose.<BR><BR>". +qq!<A HREF="${p}edit/reason.html?class=$class">!. +"<I>Add a $classname reason</I></A><BR><BR>"; + +my $where_clause = " WHERE class='$class' "; + +my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' . + 'reason_type.typenum = reason.reason_type ' . $where_clause; + +my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ]; + +</%init> diff --git a/httemplate/browse/reason_type.html b/httemplate/browse/reason_type.html new file mode 100644 index 000000000..6b444bad1 --- /dev/null +++ b/httemplate/browse/reason_type.html @@ -0,0 +1,68 @@ +<% include( 'elements/browse.html', + 'title' => ucfirst($classname) . " Reason Types", + 'menubar' => [ ucfirst($classname) . " reasons" => + $p.'browse/reason.html?class=' . $class, + ], + 'html_init' => $html_init, + 'name' => $classname . " reason types", + 'query' => { 'table' => 'reason_type', + 'hashref' => {}, + 'extra_sql' => $where_clause . + 'ORDER BY typenum', + }, + 'count_query' => $count_query, + 'header' => [ '#', + ucfirst($classname) . ' Reason Type', + ucfirst($classname) . ' Reasons', + ], + 'fields' => [ 'typenum', + 'type', + $reasons_sub, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +$cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +my $class=$1; + +my $classname = $FS::reason_type::class_name{$class}; + +my $html_init = ucfirst($classname) . + " reason types allow groups of $classname reasons for reporting purposes." . + qq!<BR><BR><A HREF="${p}edit/reason_type.html?class=$class"><I>Add a ! . + $classname . " reason type</I></A><BR><BR>"; + +my $reasons_sub = sub { + my $reason_type = shift; + + [ map { + [ + { + 'data' => $_->reason, + 'align' => 'left', + 'link' => $p. "edit/reason.html?class=$class&reasonnum=". + $_->reasonnum, + }, + ]; + } + $reason_type->enabled_reasons, + + ]; + +}; + +my $where_clause = "WHERE class='$class'"; +my $count_query = 'SELECT COUNT(*) FROM reason_type '; +$count_query .= $where_clause; + +my $link = [ $p.'edit/reason_type.html?class='.$class.'&typenum=', 'typenum' ]; + +</%init> diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi new file mode 100644 index 000000000..541e967dd --- /dev/null +++ b/httemplate/browse/router.cgi @@ -0,0 +1,52 @@ +<% include('elements/browse.html', + 'title' => 'Routers', + 'menubar' => [ @menubar ], + 'name_singular' => 'router', + 'query' => { 'table' => 'router', + 'hashref' => {}, + 'extra_sql' => $extra_sql, + }, + 'count_query' => "SELECT count(*) from router $count_sql", + 'header' => [ 'Router name', + 'Address block(s)', + ], + 'fields' => [ 'routername', + sub { join( '<BR>', map { $_->NetAddr } + shift->addr_block + ); + }, + ], + 'links' => [ [ "${p2}edit/router.cgi?", 'routernum' ], + '', + ], + 'agent_virt' => 1, + 'agent_null_right'=> "Broadband global configuration", + 'agent_pos' => 1, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Broadband configuration') + || $FS::CurrentUser::CurrentUser->access_right('Broadband global configuration'); + +my $p2 = popurl(2); +my $extra_sql = ''; + +my @menubar = ( 'Add a new router', "${p2}edit/router.cgi" ); + +if ($cgi->param('hidecustomerrouters') eq '1') { + $extra_sql = 'WHERE svcnum > 0'; + $cgi->param('hidecustomerrouters', 0); + push @menubar, 'Show customer routers', $cgi->self_url(); +} else { + $cgi->param('hidecustomerrouters', 1); + push @menubar, 'Hide customer routers', $cgi->self_url(); +} + +my $count_sql = $extra_sql. ( $extra_sql =~ /WHERE/ ? ' AND' : 'WHERE' ). + $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'Broadband global configuration', + ); + +</%init> diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi new file mode 100755 index 000000000..c6e615d40 --- /dev/null +++ b/httemplate/browse/svc_acct_pop.cgi @@ -0,0 +1,77 @@ +<% include( 'elements/browse.html', + 'title' => 'Access Numbers', + 'html_init' => $html_init, + 'name_singular' => 'access number', + 'query' => $query, + 'count_query' => $count_query, + 'header' => [ + '#', + 'City', + 'State', + 'Area code', + 'Exchange', + 'Local', + 'Accounts', + ], + 'fields' => [ + 'popnum', + 'city', + 'state', + 'ac', + 'exch', + 'loc', + $num_accounts_sub, + ], + 'align' => 'rllrrrr', + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Dialup configuration') + || $curuser->access_right('Dialup global configuration'); + +my $html_init = qq! + <A HREF="${p}edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A> + <BR><BR> +!; + +my $query = { 'select' => '*, + ( SELECT COUNT(*) FROM svc_acct + WHERE svc_acct.popnum = svc_acct_pop.popnum + ) AS num_accounts + ', + 'table' => 'svc_acct_pop', + #'hashref' => { 'disabled' => '' }, + 'extra_sql' => 'ORDER BY state, city, ac, exch, loc', + }; + +my $count_query = "SELECT COUNT(*) FROM svc_acct_pop"; # WHERE DISABLED IS NULL OR DISABLED = ''"; + +my $svc_acct_pop_link = [ $p.'edit/svc_acct_pop.cgi?', 'popnum' ]; + +my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='; + +my $num_accounts_sub = sub { + my $svc_acct_pop = shift; + [ + [ + { 'data' => '<B><FONT COLOR="#00CC00">'. + $svc_acct_pop->get('num_accounts'). + '</FONT></B>', + 'align' => 'right', + }, + { 'data' => 'active', + 'align' => 'left', + 'link' => ( $svc_acct_pop->get('num_accounts') + ? $svc_acct_link. $svc_acct_pop->popnum + : '' + ), + }, + ], + ]; +}; + +</%init> diff --git a/httemplate/browse/tax_class.html b/httemplate/browse/tax_class.html new file mode 100755 index 000000000..76d266bf9 --- /dev/null +++ b/httemplate/browse/tax_class.html @@ -0,0 +1,92 @@ +<% include( 'elements/browse.html', + 'title' => "Tax classes $title", + 'name_singular' => 'tax class', + 'menubar' => \@menubar, + 'html_init' => $html_init, + 'query' => { + 'table' => 'tax_class', + 'hashref' => $hashref, + 'extra_sql' => $where, + 'order_by' => 'ORDER BY taxclass', + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'align' => $align, + 'links' => \@links, + 'link_onclicks' => \@link_onclicks, + 'disable_maxselect' => 1, + 'disable_total' => 1, + ) +%> +<%once> + +my $conf = new FS::Conf; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $title = ''; +my @menubar = (); +my $html_init = ''; +my $hashref = {}; +my @where = (); +my $onclick = 'return true;'; + +my $omit = ''; +if ( $cgi->param('magic') eq 'omit' ) { + $cgi->param('omit') =~ /^([,\d]+)$/; + $omit = $1; + $title .= " unselected"; + push @where, map { "taxclassnum != $_" } grep {$_} split( /,/, $omit ); + $onclick = sub{ 'parent.doSelect('. shift->taxclassnum. '); return false;' } +} +$cgi->delete('omit'); + +my $data_vendor = ''; +if ( $cgi->param('datavendor') =~ /^([\w]+)$/ ) { + $data_vendor = $1; + $title .= " for data vendor $1"; + push @where, 'data_vendor = '. dbh->quote($data_vendor); +} +$cgi->delete('data_vendor'); + +my $selected = ''; +if ( $cgi->param('magic') eq 'select') +{ + $cgi->param('selected') =~ /^([,\d]*)$/; + $selected = $1; + $title = " selected"; + my @clauses = map { "taxclassnum = $_" } grep {$_} split( /,/, $selected ); + @where = scalar(@clauses) ? '( '. join(' OR ', @clauses) .')' : '1=0'; + $onclick = sub{ 'parent.doUnselect('. shift->taxclassnum. '); return false;' } ; +} +$cgi->delete('selected'); + + +if ( $data_vendor ) { + push @menubar, 'View all tax classes' => $p.'browse/tax_class.html'; +} + +$cgi->param('dummy', 1); + +#restore this so pagination works +$cgi->param('omit', $omit ) if $omit; +$cgi->param('selected', $selected ) if $selected; +$cgi->param('data_vendor', $data_vendor ) if $data_vendor; + +my $where = scalar(@where) ? 'WHERE '. join( ' AND ', @where ) : ''; +my $count_query = 'SELECT COUNT(*) FROM tax_class '. $where; + +my $link = [ 'javascript:void(0);', sub{ ''; } ]; + +my @header = ( '', '', '' ); +my @links = ( $link, $link, $link ); +my @link_onclicks = ( $onclick, $onclick, $onclick ); +my $align = 'lll'; +my @fields = ( 'data_vendor', 'taxclass', 'description' ); + +</%init> diff --git a/httemplate/browse/tax_rate.cgi b/httemplate/browse/tax_rate.cgi new file mode 100755 index 000000000..cb997fada --- /dev/null +++ b/httemplate/browse/tax_rate.cgi @@ -0,0 +1,348 @@ +<% include( 'elements/browse.html', + 'title' => "Tax Rates $title", + 'name_singular' => 'tax rate', + 'menubar' => \@menubar, + 'html_init' => $html_init, + 'html_form' => $html_form, + 'disableable' => 1, + 'disabled_statuspos' => 5, + 'query' => $query, + 'count_query' => $count_query, + 'header' => \@header, + 'header2' => \@header2, + 'fields' => \@fields, + 'align' => $align, + 'color' => \@color, + 'cell_style' => \@cell_style, + 'links' => \@links, + 'link_onclicks' => \@link_onclicks, + ) +%> +<%once> + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $rate_sub = sub { + my $tax_rate = shift; + + my $units = $tax_rate->unittype_name; + $units =~ s/ / /g; + + my @rate = (); + push @rate, + ($tax_rate->tax * 100). '% <FONT SIZE="-1">(edit)</FONT>' + if $tax_rate->tax > 0 || $tax_rate->taxbase > 0; + push @rate, + ($tax_rate->excessrate * 100). '% <FONT SIZE="-1">(edit)</FONT>' + if $tax_rate->excessrate > 0; + push @rate, + $money_char. $tax_rate->fee. + qq! per $units<FONT SIZE="-1">(edit)</FONT>! + if $tax_rate->fee > 0 || $tax_rate->feebase > 0; + push @rate, + $money_char. $tax_rate->excessfee. + qq! per $units<FONT SIZE="-1">(edit)</FONT>! + if $tax_rate->excessfee > 0; + + + [ map [ {'data'=>$_} ], @rate ]; +}; + +my $limit_sub = sub { + my $tax_rate = shift; + + my $maxtype = $tax_rate->maxtype_name; + $maxtype =~ s/ / /g; + + my $units = $tax_rate->unittype_name; + $units =~ s/ / /g; + + my @limit = (); + push @limit, + sprintf("$money_char%.2f %s", $tax_rate->taxbase, $maxtype ) + if $tax_rate->taxbase > 0; + push @limit, + sprintf("$money_char%.2f tax", $tax_rate->taxmax ) + if $tax_rate->taxmax > 0; + push @limit, + $tax_rate->feebase. " $units". ($tax_rate->feebase == 1 ? '' : 's') + if $tax_rate->feebase > 0; + push @limit, + $tax_rate->feemax. " $units". ($tax_rate->feebase == 1 ? '' : 's') + if $tax_rate->feemax > 0; + + push @limit, 'Excluding setup fee' + if $tax_rate->setuptax =~ /^Y$/i; + + push @limit, 'Excluding recurring fee' + if $tax_rate->recurtax =~ /^Y$/i; + + [ map [ {'data'=>$_} ], @limit ]; +}; + +my $oldrow; +my $cell_style; +my $cell_style_sub = sub { + my $row = shift; + if ( $oldrow ne $row ) { + if ( $oldrow ) { + if ( $oldrow->country ne $row->country ) { + $cell_style = 'border-top:1px solid #000000'; + } elsif ( $oldrow->state ne $row->state ) { + $cell_style = 'border-top:1px solid #cccccc'; #default? + } elsif ( $oldrow->state eq $row->state ) { + #$cell_style = 'border-top:dashed 1px dark gray'; + $cell_style = 'border-top:1px dashed #cccccc'; + } + } + $oldrow = $row; + } + return $cell_style; +}; + +my $select_link = [ 'javascript:void(0);', sub { ''; } ]; + +my $select_onclick = sub { + my $row = shift; + my $taxnum = $row->taxnum; + my $color = '#333399'; + qq!overlib( OLiframeContent('${p}edit/tax_rate.html?$taxnum', 540, 620, 'edit_tax_rate_popup' ), CAPTION, 'Edit tax rate', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color' ); return false;!; +}; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my @menubar; +my $title = ''; + +my $data_vendor = ''; +if ( $cgi->param('data_vendor') =~ /^(\w+)$/ ) { + $data_vendor = $1; + $title = "$data_vendor"; +} +$cgi->delete('data_vendor'); + +my $geocode = ''; +if ( $cgi->param('geocode') =~ /^(\w+)$/ ) { + $geocode = $1; + $title = " geocode $geocode"; +} +$cgi->delete('geocode'); + +$title = " for $title" if $title; + +my $taxclassnum = ''; +if ( $cgi->param('taxclassnum') =~ /^(\d+)$/ ) { + $taxclassnum = $1; + my $tax_class = qsearchs('tax_class', {'taxclassnum' => $taxclassnum}); + if ($tax_class) { + $title .= " for ". $tax_class->taxclass. + " (". $tax_class->description. ") tax class"; + }else{ + $taxclassnum = ''; + } +} +$cgi->delete('taxclassnum'); + +my $tax_type = $1 + if ( $cgi->param('tax_type') =~ /^(\d+)$/ ); +my $tax_cat = $1 + if ( $cgi->param('tax_cat') =~ /^(\d+)$/ ); + +if ($tax_type || $tax_cat ) { + my $compare = "LIKE '". ( $tax_type || "%" ). ":". ( $tax_cat || "%" ). "'"; + $compare = "= '$tax_type:$tax_cat'" if ($tax_type && $tax_cat); + my @tax_class = + qsearch({ 'table' => 'tax_class', + 'hashref' => {}, + 'extra_sql' => "WHERE taxclass $compare", + }); + if (@tax_class) { + $tax_class[0]->description =~ /^(.*):(.*)/; + $title .= " for"; + $title .= " $tax_type ($1) tax type" if $tax_type; + $title .= " and" if ($tax_type && $tax_cat); + $title .= " $tax_cat ($2) tax category" if $tax_cat; + }else{ + $tax_type = ''; + $tax_cat = ''; + } +} +$cgi->delete('tax_type'); +$cgi->delete('tax_cat'); + +if ( $geocode || $taxclassnum ) { + push @menubar, 'View all tax rates' => $p.'browse/tax_rate.cgi'; +} + +$cgi->param('dummy', 1); + +#restore this so pagination works +$cgi->param('data_vendor', $data_vendor) if $data_vendor; +$cgi->param('geocode', $geocode) if $geocode; +$cgi->param('taxclassnum', $taxclassnum ) if $taxclassnum; +$cgi->param('tax_type', $tax_type ) if $tax_type; +$cgi->param('tax_cat', $tax_cat ) if $tax_cat; + +my $html_form = include('/elements/init_overlib.html'). '<BR><BR>'. + join(' ', + map { + include('/elements/popup_link.html', + { + 'action' => $p. "misc/enable_or_disable_tax.html?action=$_&". + $cgi->query_string, + 'label' => ucfirst($_). ' all these taxes', + 'actionlabel' => ucfirst($_). ' taxes', + }, + ); + } + qw(disable enable) + ); + +my ($query, $count_query) = FS::tax_rate::browse_queries(scalar($cgi->Vars)); + +$cell_style = ''; + +my @header = ( 'Location Code', ); +my @header2 = ( '', ); +my @links = ( '', ); +my @link_onclicks = ( '', ); +my $align = 'l'; + +my @fields = ( + 'geocode', +); + +my @color = ( + '000000', +); + +push @header, qq!Tax class (<A HREF="${p}edit/tax_class.html">add new</A>)!; +push @header2, '(per-tax classification)'; +push @fields, 'taxclass_description'; +push @color, '000000'; +push @links, ''; +push @link_onclicks, ''; +$align .= 'l'; + +push @header, 'Tax name', + 'Rate', #'Tax', + 'Limits', + ; + +push @header2, '(printed on invoices)', + '', + '', + ; + +push @fields, + sub { shift->taxname || 'Tax' }, + $rate_sub, + $limit_sub, +; + +push @color, + sub { shift->taxname ? '000000' : '666666' }, + sub { shift->tax ? '000000' : '666666' }, + '000000', +; + +$align .= 'lrl'; + +my @cell_style = map $cell_style_sub, (1..scalar(@header)); + +push @links, '', $select_link, ''; +push @link_onclicks, '', $select_onclick, ''; + +my $html_init = ''; + +$html_init .= qq( + <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws_iframe.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/overlibmws_draggable.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="${fsurl}elements/iframecontentmws.js"></SCRIPT> + +); + +$html_init .= qq( + <FORM> + <TABLE> + <TR> + <TD><SELECT NAME="data_vendor" onChange="this.form.submit()"> +); + +my $sql = "SELECT DISTINCT data_vendor FROM tax_rate ORDER BY data_vendor"; +my $dbh = dbh; +my $sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (['(choose data vendor)'], @{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $data_vendor ? " SELECTED" : ""). + '">'. $_->[0]; +} +$html_init .= qq( + </SELECT> + + <TD><INPUT NAME="geocode" TYPE="text" SIZE="12" VALUE="$geocode"></TD> + +<!-- generic + <TD><INPUT NAME="taxclassnum" TYPE="text" SIZE="12" VALUE="$taxclassnum"></TD> + <TD><INPUT TYPE="submit" VALUE="Filter by tax_class"></TD> +--> + +<!-- cch specific --> + <TD><SELECT NAME="tax_type" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + "substring(taxclass from 1 for position(':' in taxclass)-1),". + "substring(description from 1 for position(':' in description)-1) ". + "FROM tax_class WHERE data_vendor='cch' ORDER BY 2"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (['', '(choose tax type)'], @{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_type ? " SELECTED" : ""). + '">'. $_->[1]; +} + +$html_init .= qq( + </SELECT> + + <TD><SELECT NAME="tax_cat" onChange="this.form.submit()"> +); + +$sql = "SELECT DISTINCT ". + "substring(taxclass from position(':' in taxclass)+1),". + "substring(description from position(':' in description)+1) ". + "from tax_class WHERE data_vendor='cch' ORDER BY 2"; + +$sth = $dbh->prepare($sql) or die $dbh->errstr; +$sth->execute or die $sth->errstr; +for (['', '(choose tax category)'], @{$sth->fetchall_arrayref}) { + $html_init .= '<OPTION VALUE="'. $_->[0]. '"'. + ($_->[0] eq $tax_cat ? " SELECTED" : ""). + '">'. $_->[1]; +} + +$html_init .= qq( + </SELECT> + + </TR> + <TR> + <TD></TD> + <TD><INPUT TYPE="submit" VALUE="Filter by geocode"></TD> + <TD></TD> + <TD></TD> + </TR> + </TABLE> + </FORM> + +); + +</%init> diff --git a/httemplate/browse/usage_class.html b/httemplate/browse/usage_class.html new file mode 100644 index 000000000..63fd2c5a2 --- /dev/null +++ b/httemplate/browse/usage_class.html @@ -0,0 +1,28 @@ +<% include( 'elements/browse.html', + 'title' => 'Usage classes', + 'html_init' => $html_init, + 'name' => 'usage classes', + 'disableable' => 1, + 'disabled_statuspos' => 2, + 'query' => { 'table' => 'usage_class', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY classnum', + }, + 'count_query' => 'SELECT COUNT(*) FROM usage_class', + 'header' => [ '#', 'Class' ], + 'fields' => [ 'classnum', 'classname' ], + 'links' => [ $link, $link ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + 'Usage classes define groups of usage for taxation purposes.<BR><BR>'. + qq!<A HREF="${p}edit/usage_class.html"><I>Add a usage class</I></A><BR><BR>!; + +my $link = [ $p.'edit/usage_class.html?', 'classnum' ]; + +</%init> |