diff options
author | Mark Wells <mark@freeside.biz> | 2012-09-29 16:36:46 -0700 |
---|---|---|
committer | Mark Wells <mark@freeside.biz> | 2012-09-29 16:36:46 -0700 |
commit | 33beebf4cb42eba3e1dd868ad5e0af102de961da (patch) | |
tree | 860712543dcc74ff2402a4ed8d73e8cd553e62d4 /httemplate | |
parent | 7ac86daf67b0a95153b736d5811f9050363f6553 (diff) |
update address standardization for cust_location changes
Diffstat (limited to 'httemplate')
240 files changed, 7737 insertions, 2778 deletions
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index 64288b830..fc9ce5413 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -25,6 +25,7 @@ full offerings (via their type).<BR><BR> <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH> <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH> <TH CLASS="grid" BGCOLOR="#cccccc">Master Customer</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Commissions</TH> <TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH> <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>Invoice<BR>Template</FONT></TH> <TH CLASS="grid" BGCOLOR="#cccccc">Customers</TH> @@ -93,6 +94,33 @@ full offerings (via their type).<BR><BR> </TD> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + + <TABLE> + +% #surprising amount of false laziness w/ edit/process/agent.cgi +% my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' }); +% foreach my $pkg_class ( '', @pkg_class ) { +% my %agent_pkg_class = ( 'agentnum' => $agent->agentnum, +% 'classnum' => $pkg_class ? $pkg_class->classnum : '' +% ); +% my $agent_pkg_class = +% qsearchs( 'agent_pkg_class', \%agent_pkg_class ) +% || new FS::agent_pkg_class \%agent_pkg_class; +% my $param = 'classnum'. $agent_pkg_class{classnum}; + + <TR> + <TD><% $agent_pkg_class->commission_percent || 0 %>%</TD> + <TD><% $pkg_class ? $pkg_class->classname : mt('(no package class)') |h %> + </TD> + </TR> + +% } + + </TABLE> + + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> % foreach my $access_group ( % map $_->access_group, % qsearch('access_groupagent', { 'agentnum' => $agent->agentnum }) diff --git a/httemplate/browse/cust_class.html b/httemplate/browse/cust_class.html index d7c622837..70a279a05 100644 --- a/httemplate/browse/cust_class.html +++ b/httemplate/browse/cust_class.html @@ -42,4 +42,11 @@ if ($sth->fetchrow_arrayref->[0]) { push @$links, $link; } +my $conf = new FS::Conf; +if ( $conf->exists('cust_class-tax_exempt') ) { + push @$header, 'Tax exempt'; + push @$fields, 'tax'; + push @$links, ''; +} + </%init> diff --git a/httemplate/browse/cust_note_class.html b/httemplate/browse/cust_note_class.html index f5d450b9f..7928199b3 100644 --- a/httemplate/browse/cust_note_class.html +++ b/httemplate/browse/cust_note_class.html @@ -3,7 +3,7 @@ 'html_init' => $html_init, 'name' => 'customer note classes', 'disableable' => 1, - 'disabled_statuspos' => 2, + 'disabled_statuspos' => 1, 'query' => { 'table' => 'cust_note_class', 'hashref' => {}, 'order_by' => 'ORDER BY classnum', diff --git a/httemplate/browse/ftp_target.html b/httemplate/browse/ftp_target.html new file mode 100644 index 000000000..4a5782058 --- /dev/null +++ b/httemplate/browse/ftp_target.html @@ -0,0 +1,56 @@ +<& elements/browse.html, + 'title' => 'FTP targets', + 'menubar' => [ 'Add a target' => $p.'edit/ftp_target.html', ], + 'name' => 'FTP targets', + 'query' => { 'table' => 'ftp_target', + 'hashref' => {}, + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Server', + 'Username', + 'Password', + 'Path', + 'Protocol', + '', #handling + ], + 'fields' => [ 'targetnum', + 'hostname', + 'username', + 'password', + 'path', + sub { + my $ftp_target = shift; + my $label; + if ($ftp_target->secure) { + $label = 'SFTP'; + $label .= ' (port '.$ftp_target->port.')' + if $ftp_target->port != 22; + } + else { + $label = 'FTP'; + $label .= ' (port '.$ftp_target->port.')' + if $ftp_target->port != 21; + } + $label; + }, + 'handling', + ], + 'links' => [ $link, $link ], +&> +</TABLE> + +<% include('/elements/footer.html') %> + +<%once> + +my $count_query = 'SELECT COUNT(*) FROM ftp_target'; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $link = [ $p.'edit/ftp_target.html?', 'targetnum' ]; +</%init> diff --git a/httemplate/browse/msg_template.html b/httemplate/browse/msg_template.html index 8a6ccf741..50afc283e 100644 --- a/httemplate/browse/msg_template.html +++ b/httemplate/browse/msg_template.html @@ -1,29 +1,32 @@ <% include( 'elements/browse.html', 'title' => 'Message templates', 'name_singular' => 'template', - 'menubar' => [ 'Add a new template' => - $p.'edit/msg_template.html', - ], - 'query' => { 'table' => 'msg_template', }, - 'count_query' => 'SELECT COUNT(*) FROM msg_template', - 'disableable' => 1, + 'menubar' => \@menubar, + 'query' => { 'table' => 'msg_template', }, + 'count_query' => 'SELECT COUNT(*) FROM msg_template', + 'disableable' => 1, 'disabled_statuspos' => 2, 'agent_virt' => 1, - 'agent_null_right' => ['Edit global templates','Configuration'], + 'agent_null_right' => ['View global templates','Edit global templates'], 'agent_pos' => 1, - 'header' => [ 'Name', '', ('' x scalar @locales) ], - 'fields' => [ 'msgname', @locales ], - 'links' => [ $link, @locale_links ], - 'cell_style' => - [ '', '', ($locale_style) x (scalar @locales) ], + 'header' => [ 'Name', '', map '', @locales ], + 'fields' => [ 'msgname', @locales ], + 'links' => [ $link, @locale_links ], + 'cell_style' => [ '', '', map $locale_style, @locales ], ) %> <%init> +my $curuser = $FS::CurrentUser::CurrentUser; + die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Edit templates') - || $FS::CurrentUser::CurrentUser->access_right('Edit global templates') - || $FS::CurrentUser::CurrentUser->access_right('Configuration'); + unless $curuser->access_right([ 'View templates', 'View global templates', + 'Edit templates', 'Edit global templates', ]); + +my @menubar = (); +if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) { + push @menubar, 'Add a new template' => $p.'edit/msg_template.html'; +} my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ]; diff --git a/httemplate/browse/part_event.html b/httemplate/browse/part_event.html index 03996435e..c06a14fe7 100644 --- a/httemplate/browse/part_event.html +++ b/httemplate/browse/part_event.html @@ -174,7 +174,7 @@ my $html_init = qq!<A HREF="${p}edit/part_event.html"><I>Add a new event</I></A>!. ' or <SELECT NAME="clone"><OPTION></OPTION>'; -foreach my $part_event ( qsearch('part_event', {'diabled'=>''}) ) { +foreach my $part_event ( qsearch('part_event', {'disabled'=>''}) ) { $html_init .= '<OPTION VALUE="'. $part_event->eventpart. '">'. $part_event->eventpart. ': '. $part_event->event. '</OPTION>'; } diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 8e28f4fc6..b7ecc00a6 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -36,10 +36,9 @@ function part_export_areyousure(href) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->exportnum %></A></TD> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> -% if( $part_export->exportname ) { - <B><% $part_export->exportname %>:</B><BR> -% } -<% $part_export->exporttype %> to <% $part_export->machine %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>)</TD> + <% $part_export->label_html %> + (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>) + </TD> <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> <% itable() %> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 4ca78d718..e3d9de13a 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -436,7 +436,7 @@ push @fields, [ map { [ - { 'data' => $_, + { 'data' => "$_: ", 'align' => 'right', }, { 'data' => $part_pkg->format($_,$options{$_}), diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html index 9cc32c459..c7374673f 100755 --- a/httemplate/browse/part_referral.html +++ b/httemplate/browse/part_referral.html @@ -6,6 +6,13 @@ Where a customer heard about your service. Tracked for informational purposes. <A HREF="<% $p %>edit/part_referral.html"><I>Add a new advertising source</I></A> <BR><BR> +<% $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled advertising sources</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled advertising sources</a> )'; } +%> + <% include('/elements/table-grid.html') %> % my $bgcolor1 = '#eeeeee'; % my $bgcolor2 = '#ffffff'; @@ -13,8 +20,12 @@ Where a customer heard about your service. Tracked for informational purposes. <TR> <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2 ROWSPAN=2>Advertising source</TH> -% if ( $show_agentnums ) { +% if ( ! $cgi->param('showdisabled') ) { + <TH CLASS="grid" BGCOLOR="#cccccc" ALIGN="center" ROWSPAN=2></TH> +% } + +% if ( $show_agentnums ) { <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Agent</TH> % } @@ -27,7 +38,7 @@ Where a customer heard about your service. Tracked for informational purposes. </TR> -%foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) { +%foreach my $part_referral ( FS::part_referral->all_part_referral(1,!scalar($cgi->param('showdisabled'))) ) { % % if ( $bgcolor eq $bgcolor1 ) { % $bgcolor = $bgcolor2; @@ -55,6 +66,16 @@ Where a customer heard about your service. Tracked for informational purposes. % } <% $part_referral->referral %><% $a ? '</A>' : '' %></TD> + +% if ( ! $cgi->param('showdisabled') ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="center"> + <% $part_referral->disabled + ? '<FONT COLOR="#FF0000"><B>DISABLED</B></FONT>' + : '<FONT COLOR="#00CC00"><B>Active</B></FONT>' + %> + </TD> +% } + % if ( $show_agentnums ) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %></TD> @@ -73,11 +94,11 @@ Where a customer heard about your service. Tracked for informational purposes. <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> <TR> <TD ALIGN="right"><B><% $num_cust %></B></TD> - <TD ALIGN="left">customers</TD> + <TD ALIGN="left"> customers </TD> </TR> <TR> <TD ALIGN="right"><B><% $num_pkg %></B></TD> - <TD ALIGN="left">packages</TD> + <TD ALIGN="left"> packages </TD> </TR> </TABLE> </TD> @@ -94,7 +115,7 @@ Where a customer heard about your service. Tracked for informational purposes. % or die dbh->errstr; <TR> - <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=3><B>Total</B></TD> + <TD BGCOLOR="#dddddd" ALIGN="center" COLSPAN=<% 2 + $show_agentnums + ! $cgi->param('showdisabled') %><B>Total</B></TD> % for my $period ( keys %after ) { % my @param = ( $today-$after{$period}, % $today+$before{$period}, @@ -108,11 +129,11 @@ Where a customer heard about your service. Tracked for informational purposes. <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> <TR> <TD ALIGN="right"><B><% $num_cust %></B></TD> - <TD ALIGN="left">customers</TD> + <TD ALIGN="left"> customers </TD> </TR> <TR> <TD ALIGN="right"><B><% $num_pkg %></B></TD> - <TD ALIGN="left">packages</TD> + <TD ALIGN="left"> packages </TD> </TR> </TABLE> </TD> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 26d090a3d..a8f4a7c84 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -141,16 +141,7 @@ function part_export_areyousure(href) { % <TR> - <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"> -<% $part_export->exportnum %>: -% if ($part_export->exportname) { -<B><% $part_export->exportname %></B> ( -% } -<% $part_export->exporttype %> to <% $part_export->machine %> -% if ($part_export->exportname) { -) -% } -</A></TD> + <TD><A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>"><% $part_export->label_html %></A></TD> </TR> % } diff --git a/httemplate/browse/part_svc_class.html b/httemplate/browse/part_svc_class.html new file mode 100644 index 000000000..73bd60305 --- /dev/null +++ b/httemplate/browse/part_svc_class.html @@ -0,0 +1,34 @@ +<% include( 'elements/browse.html', + 'title' => 'Service classes', + 'html_init' => $html_init, + 'name' => 'service classes', + 'disableable' => 1, + 'disabled_statuspos' => 1, + 'query' => { 'table' => 'part_svc_class', + 'hashref' => {}, + 'order_by' => 'ORDER BY classnum', + }, + 'count_query' => $count_query, + 'header' => $header, + 'fields' => $fields, + 'links' => $links, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + 'Service classes are user-defined, informational types for services.<BR><BR>'. + qq!<A HREF="${p}edit/part_svc_class.html"><I>Add a service class</I></A><BR><BR>!; + +my $count_query = 'SELECT COUNT(*) FROM part_svc_class'; + +my $link = [ $p.'edit/part_svc_class.html?', 'classnum' ]; + +my $header = [ '#', 'Class' ]; +my $fields = [ 'classnum', 'classname' ]; +my $links = [ $link, $link ]; + +</%init> diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html index a06e5cf7c..7a8a668d7 100644 --- a/httemplate/browse/payment_gateway.html +++ b/httemplate/browse/payment_gateway.html @@ -77,9 +77,9 @@ my $options_sub = sub { my $html = '<TABLE CELLSPACING=0 CELLPADDING=0>'; - my %options = $payment_gateway->options; + tie my %options, 'Tie::IxHash', $payment_gateway->options; foreach my $option ( keys %options ) { - $html .= '<TR><TH>'. $option. ':</TH>'. + $html .= '<TR><TH ALIGN="right">'. $option. ':</TH>'. '<TD>'. $options{$option}. '</TD></TR>'; } $html .= '</TABLE>'; diff --git a/httemplate/browse/radius_group.html b/httemplate/browse/radius_group.html index fbf6d3766..98e81ab86 100644 --- a/httemplate/browse/radius_group.html +++ b/httemplate/browse/radius_group.html @@ -5,15 +5,26 @@ 'query' => { 'table' => 'radius_group' }, 'count_query' => 'SELECT COUNT(*) FROM radius_group', 'header' => [ '#', 'RADIUS Group', 'Description', 'Priority', - 'Check', 'Reply' ], + 'Check', 'Reply', 'Speed' ], 'fields' => [ 'groupnum', 'groupname', 'description', 'priority', - $check_attr, $reply_attr + $check_attr, $reply_attr, + sub { + my $group = shift; + if ($group->speed_down and $group->speed_up) { + return join (' / ', $group->speed_down, $group->speed_up); + } elsif ( $group->speed_down ) { + return $group->speed_down . ' down'; + } elsif ( $group->speed_up ) { + return $group->speed_up . ' up'; + } + ''; + }, ], - 'align' => 'lllcll', - 'links' => [ $link, $link, '', '', '', '', + 'align' => 'lllcllc', + 'links' => [ $link, $link, '', '', '', '', '' ], &> <%init> diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html index fe285be4a..14e97bf2f 100644 --- a/httemplate/browse/reason.html +++ b/httemplate/browse/reason.html @@ -17,14 +17,17 @@ 'header' => [ '#', ucfirst($classname) . ' Reason Type', ucfirst($classname) . ' Reason', + ($class eq 'S' ? 'Unsuspension Fee' : ()), ], 'fields' => [ 'reasonnum', sub { shift->reasontype->type }, 'reason', + $unsuspend_pkg_comment, ], 'links' => [ $link, $link, '', + $unsuspend_pkg_link, ], ) %> @@ -50,4 +53,18 @@ my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' . my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ]; +my ($unsuspend_pkg_comment, $unsuspend_pkg_link); +if ( $class eq 'S' ) { + $unsuspend_pkg_comment = sub { + my $pkgpart = shift->unsuspend_pkgpart or return ''; + my $part_pkg = FS::part_pkg->by_key($pkgpart) or return ''; + $part_pkg->pkg_comment; + }; + + my $unsuspend_pkg_link = sub { + my $pkgpart = shift->unsuspend_pkgpart or return ''; + [ $p."edit/part_pkg.cgi?", $pkgpart ]; + }; +} + </%init> diff --git a/httemplate/browse/sales.cgi b/httemplate/browse/sales.cgi new file mode 100755 index 000000000..af098121d --- /dev/null +++ b/httemplate/browse/sales.cgi @@ -0,0 +1,100 @@ +<% include("/elements/header.html",'Sales Listing', menubar( + 'Add new sales person' => '../edit/sales.cgi' +)) %> +Sales people bring in business.<BR><BR> +% if ( dbdef->table('sales')->column('disabled') ) { + + <% $cgi->param('showdisabled') + ? do { $cgi->param('showdisabled', 0); + '( <a href="'. $cgi->self_url. '">hide disabled sales people</a> )'; } + : do { $cgi->param('showdisabled', 1); + '( <a href="'. $cgi->self_url. '">show disabled sales people</a> )'; } + %> +% } + + +<% include('/elements/table-grid.html') %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=<% ( $cgi->param('showdisabled') || !dbdef->table('sales')->column('disabled') ) ? 2 : 3 %>>Sales person</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Agent</TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Access Groups</TH> +</TR> + +%foreach my $sales ( sort { +% $a->getfield('salesnum') cmp $b->getfield('salesnum') +%} qsearch('sales', \%search ) ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<%$p%>edit/sales.cgi?<% $sales->salesnum %>"><% $sales->salesnum %></A> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<%$p%>edit/sales.cgi?<% $sales->salesnum %>"><% $sales->salesperson %></A> + </TD> + +% if ( ! $cgi->param('showdisabled') ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="center"> + <% $sales->disabled ? '<FONT COLOR="#FF0000"><B>DISABLED</B></FONT>' + : '<FONT COLOR="#00CC00"><B>Active</B></FONT>' + %> + </TD> +% } + +% my ($agent) = qsearch('agent', { 'agentnum' => $sales->agentnum }); + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<%$p%>edit/sales.cgi?<% $sales->agentnum %>"><% $sales->agentnum %></A> + <A HREF="<%$p%>edit/agent.cgi?<% $agent->agentnum %>">(<% $agent->agent %>)<BR> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% foreach my $access_group ( +% map $_->access_group, +% qsearch('access_groupsales', { 'salesnum' => $sales->salesnum }) +% ) { + <A HREF="<%$p%>edit/access_group.html?<% $access_group->groupnum %>"><% $access_group->groupname |h %><BR> +% } + </TD> + + </TR> +% } + + </TABLE> + +<SCRIPT TYPE="text/javascript"> + function areyousure(what, href) { + if ( confirm("Are you sure you want to " + what + "?") == true ) + window.location.href = href; + } +</SCRIPT> + + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %search; +if ( $cgi->param('showdisabled') + || !dbdef->table('agent')->column('disabled') ) { + %search = (); +} else { + %search = ( 'disabled' => '' ); +} + +my $conf = new FS::Conf; + +</%init> diff --git a/httemplate/browse/tower.html b/httemplate/browse/tower.html index 7767a3c36..7f096a748 100644 --- a/httemplate/browse/tower.html +++ b/httemplate/browse/tower.html @@ -8,9 +8,10 @@ 'count_query' => 'SELECT COUNT(*) FROM tower', 'disableable' => 1, 'disabled_statuspos' => 1, - 'header' => [ 'Name', 'Sectors', ], + 'header' => [ 'Name', 'Sectors', 'Coordinates'], 'fields' => [ $tower_sub, $sector_sub, + $coord_sub, ], 'links' => [ ], ) @@ -49,6 +50,19 @@ my $num_svc_links = sub { }, }; +my $coord_sub = sub { + my $tower = shift; + + my $coords = $m->scomp("/elements/coord-links.html", $tower->latitude, $tower->longitude, $tower->towername); + + [ + [ + { 'data' => "Latitude: " . $tower->latitude . "<br>Longitude: " . $tower->longitude, }, + { 'data' => $coords, 'link' => "Coordinates", }, + ], + ] +}; + my $tower_sub = sub { my $tower = shift; my $sectors = join(',', diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi index 3dcb1d3ef..f1cbb1831 100644 --- a/httemplate/config/config-process.cgi +++ b/httemplate/config/config-process.cgi @@ -47,7 +47,7 @@ '</pre></font>'; % } elsif ( $type eq 'checkbox' ) { -% if ( $conf->exists($i->key, $agentnum) ) { +% if ( $conf->config_bool($i->key, $agentnum) ) { configCell.style.backgroundColor = '#00ff00'; configCell.innerHTML = 'YES'; % } else { @@ -184,7 +184,7 @@ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { } # warn @touch; $conf->touch($_, $agentnum) foreach @touch; -$conf->delete($_, $agentnum) foreach @delete; +$conf->delete_bool($_, $agentnum) foreach @delete; if (scalar(@error)) { $cgi->param('error', join(' ', @error)); diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index 02acd5853..02a24adbf 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -211,7 +211,7 @@ invoice language options: % } elsif ( $type eq 'checkbox' ) { <tr> - <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td> + <td id="<% $agentnum.$i->key.$n %>" bgcolor="#<% $conf->config_bool($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td> </tr> % } elsif ( $type eq 'select' && $i->select_hash ) { diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 6a1eaecf7..7960d7e38 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -40,12 +40,14 @@ Setting <b><% $key %></b> <table><tr><td> % my $n = 0; +% my $submit = 0; % foreach my $type (@types) { % if ( $type eq '' ) { <font color="#ff0000">no type</font> % } elsif ( $type eq 'image' ) { +% $submit++; <% $conf->exists($key, $agentnum) ? 'Current image<br>'. @@ -59,24 +61,37 @@ Setting <b><% $key %></b> New image filename <input type="file" name="<% "$key$n" %>"> % } elsif ( $type eq 'binary' ) { +% $submit++; Filename <input type="file" name="<% "$key$n" %>"> % } elsif ( $type eq 'textarea' ) { +% $submit++; <textarea name="<% "$key$n" %>" rows=12 cols=78 wrap="off"><% join("\n", $conf->config($key, $agentnum)) |h %></textarea> % } elsif ( $type eq 'checkbox' ) { +% +% if ( $agentnum && $conf->exists($key) && ! $agent_bool ) { - <input name="<% "$key$n" %>" type="checkbox" value="1" - <% $conf->exists($key, $agentnum) ? 'CHECKED' : '' %> > + <input name="<% "$key$n" %>" type="checkbox" value="1" CHECKED DISABLED> + <FONT SIZE="-1"><I>(global setting cannot yet be overridden)</I></FONT> -% } elsif ( $type eq 'text' ) { +% } else { +% $submit++; - <input name="<% "$key$n" %>" type="text" value="<% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '' |h %>"> + <input name="<% "$key$n" %>" type="checkbox" value="1" + <% $conf->config_bool($key, $agentnum) ? 'CHECKED' : '' %> > +% } -% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { +% } elsif ( $type eq 'text' ) { +% $submit++; + <input name="<% "$key$n" %>" type="text" value="<% $conf->exists($key, $agentnum) ? $conf->config($key, $agentnum) : '' |h %>"> + +% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { +% $submit++; + <select name="<% "$key$n" %>" <% $type eq 'selectmultiple' ? 'MULTIPLE' : '' %>> % @@ -131,7 +146,8 @@ Setting <b><% $key %></b> </select> -% } elsif ( $type eq 'select-sub' ) { +% } elsif ( $type eq 'select-sub' ) { +% $submit++; <select name="<% "$key$n" %>" <% $config_item->multiple ? 'MULTIPLE' : '' %>> @@ -167,8 +183,8 @@ Setting <b><% $key %></b> </select> -% } elsif ( $type eq 'editlist' ) { -% +% } elsif ( $type eq 'editlist' ) { +% $submit++; <script> function doremove<% "$key$n" %>() { fromObject = document.OneTrueForm.<% "$key$n" %>; @@ -284,10 +300,10 @@ Setting <b><% $key %></b> </tr></table> % } elsif ( $element_types{$type} ) { +% $submit++; % % my %opt = ( 'element_name' => "$key$n", % 'empty_label' => ' ', -% 'showdisabled' => 1, % ); % if ( $config_item->multiple ) { % $opt{'multiple'} = 1 if $config_item->multiple; @@ -313,7 +329,10 @@ Setting <b><% $key %></b> % } </tr> </table> -<INPUT TYPE="submit" VALUE="<% $title %>"> + +% if ( $submit ) { + <INPUT TYPE="submit" VALUE="<% $title %>"> +% } </FORM> </BODY> @@ -365,5 +384,6 @@ my $config_item = $confitems{$key}; my $description = $config_item->description; my $config_type = $config_item->type; my @types = ref($config_type) ? @$config_type : ($config_type); +my $agent_bool = $config_item->agent_bool; </%init> diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html index e3bb28298..33b21a3ad 100644 --- a/httemplate/docs/about.html +++ b/httemplate/docs/about.html @@ -10,7 +10,7 @@ % my $url = $conf->config('company_url', $agentnum); % if ( $url ) { <BR><BR> - <A HREF="<% $conf->config('company_url', $agentnum) %>" TARGET="_blank"><%$title%> homepage</A> + <FONT SIZE="+1"><A HREF="<% $conf->config('company_url', $agentnum) %>" TARGET="_top"><%$title%> homepage</A></FONT> % } % } else { @@ -46,7 +46,7 @@ GNU <b>Affero</b> General Public License.<BR> <BR><BR> -<A HREF="http://www.freeside.biz/freeside" TARGET="_blank">Freeside homepage</A> +<A HREF="http://www.freeside.biz/freeside" TARGET="_top">Freeside homepage</A> % if ( $agentnum ) { </FONT> % } @@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR> % unless ( $agentnum ) { <CENTER> - <FONT SIZE="-3">"A selfish heart is trouble, but a foolish heart is worse" -R. Hunter</FONT> +<!-- <FONT SIZE="-3">"" -R. Hunter</FONT> --> </CENTER> % } diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html index fab8cd09f..e40b2436b 100644 --- a/httemplate/docs/license.html +++ b/httemplate/docs/license.html @@ -6,7 +6,7 @@ <P> -Copyright © 2005-2009 Freeside Internet Services, Inc.<BR> +Copyright © 2005-2012 Freeside Internet Services, Inc.<BR> Copyright © 2000-2005 Ivan Kohler<BR> Copyright © 1999 Silicon Interactive Software Design<BR> All rights reserved<BR> diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html new file mode 100644 index 000000000..48841f550 --- /dev/null +++ b/httemplate/docs/part_svc-table.html @@ -0,0 +1,63 @@ +<& /elements/header-popup.html &> + +<TABLE> + <TR> + <TH ALIGN="left">Generic</TH> + <TH ALIGN="left">Access</TH> + <TH ALIGN="left">Telephony</TH> +<!-- <TH>Hosting</TH> + <TH>Colocation</TH> +--> + </TR> + <TR> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_acct</B>: Accounts - anything with a username (mailbox, shell, RADIUS, etc.) + <LI><B>svc_hardware</B>: Equipment supplied to customers + <LI><B>svc_external</B>: Externally-tracked service + </UL> + </TD> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_dsl</B>: DSL + <LI><B>svc_broadband</B>: Wireless broadband + <LI><B>svc_dish</B>: DISH Network + </UL> + </TD> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_phone</B>: Customer phone number + <LI><B>svc_pbx</B>: Customer PBX + </UL> + </TD> + </TR> +</TABLE> +<BR> +<TABLE> + <TR> + <TH ALIGN="left">Hosting</TH> + <TH ALIGN="left">Colocation</TH> + </TR> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_domain</B>: Domain + <LI><B>svc_cert</B>: Certificate + <LI><B>svc_forward</B>: Mail forwarding + <LI><B>svc_mailinglist</B>: Mailing list + <LI><B>svc_www</B>: Virtual domain website + </UL> + </TD> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_port</B>: Customer router/switch port + </UL> + </TD> + </TR> +<TABLE> +<!-- <LI>svc_charge - One-time charges (Partially unimplemented) + <LI>svc_wo - Work orders (Partially unimplemented) +--> + +</BODY> +</HTML> + diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi index 6707d66a4..b043d1efe 100755 --- a/httemplate/edit/agent.cgi +++ b/httemplate/edit/agent.cgi @@ -19,9 +19,12 @@ </SCRIPT> <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>"> -Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %> -<% &ntable("#cccccc", 2, '') %> +<FONT CLASS="fsinnerbox-title"> + Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %> +</FONT> + +<TABLE CLASS="fsinnerbox"> <TR> <TH ALIGN="right">Agent</TH> @@ -117,8 +120,13 @@ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %> </TR> % } +</TABLE> +<BR> + +<FONT CLASS="fsinnerbox-title"><% mt('Access Groups') |h %></FONT> +<TABLE CLASS="fsinnerbox"> + <TR> - <TD ALIGN="right">Access Groups</TD> <TD><% include('/elements/checkboxes-table.html', 'source_obj' => $agent, 'link_table' => 'access_groupagent', @@ -131,6 +139,38 @@ Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %> </TR> </TABLE> +<BR> + +<FONT CLASS="fsinnerbox-title"><% mt('Commissions') |h %></FONT> +<TABLE CLASS="fsinnerbox"> + +% #surprising amount of false laziness w/ edit/process/agent.cgi +% my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' }); +% foreach my $pkg_class ( '', @pkg_class ) { +% my %agent_pkg_class = ( 'agentnum' => $agent->agentnum, +% 'classnum' => $pkg_class ? $pkg_class->classnum : '' +% ); +% my $agent_pkg_class = +% qsearchs( 'agent_pkg_class', \%agent_pkg_class ) +% || new FS::agent_pkg_class \%agent_pkg_class; +% my $param = 'classnum'. $agent_pkg_class{classnum}; + + <TR> + <TD><INPUT TYPE = "text" + NAME = "<% $param %>" + VALUE = "<% $cgi->param($param) || $agent_pkg_class->commission_percent |h %>" + SIZE = 6 + MAXLENGTH = 7 + >% + </TD> + <TD><% $pkg_class ? $pkg_class->classname : mt('(no package class)') |h %> + </TD> + </TR> + +% } + +</TABLE> + <BR> <INPUT TYPE="submit" VALUE="<% $agent->agentnum ? "Apply changes" : "Add agent" %>"> diff --git a/httemplate/edit/cust_class.html b/httemplate/edit/cust_class.html index fdb58e687..8fce90588 100644 --- a/httemplate/edit/cust_class.html +++ b/httemplate/edit/cust_class.html @@ -1,5 +1,24 @@ <% include( 'elements/class_Common.html', - 'name' => 'Customer Class', - 'table' => 'cust_class', + 'name' => 'Customer Class', + 'table' => 'cust_class', + 'addl_fields' => \@addl_fields, + 'addl_labels' => { 'tax' => 'Tax Exempt' }, ) %> +<%init> + +my $conf = new FS::Conf; + +my @addl_fields = (); +if ( $conf->exists('cust_class-tax_exempt') ) { + push @addl_fields, { 'field' => 'tax', + 'type' => 'checkbox', + 'value' => 'Y', + }; +} else { + push @addl_fields, { 'field' => 'tax', + 'type' => 'hidden', + }; +} + +</%init> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index ca9f8677b..f744884d7 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -23,120 +23,93 @@ % } %# agent, agent_custid, refnum (advertising source), referral_custnum +%# better section title for this? +<FONT CLASS="fsinnerbox-title"><% mt('Basics') |h %></FONT> <& cust_main/top_misc.html, $cust_main, 'custnum' => $custnum &> %# birthdate -% if ( $conf->exists('cust_main-enable_birthdate') ) { +% if ( $conf->config('national_id-country') +% || $conf->exists('cust_main-enable_birthdate') +% || $conf->exists('cust_main-enable_spouse_birthdate') +% || $conf->exists('cust_main-enable_anniversary_date') +% ) +% { <BR> <& cust_main/birthdate.html, $cust_main &> % } - -%# contact info - -% my $same_checked = ''; -% my $ship_disabled = ''; -% my @ship_style = (); -% unless ( $cust_main->ship_last && $same ne 'Y' ) { -% $same_checked = 'CHECKED'; -% $ship_disabled = 'DISABLED'; -% push @ship_style, 'background-color:#dddddd'; -% foreach ( -% qw( last first company address1 address2 city county state zip country -% latitude longitude coord_auto -% daytime night fax mobile ) -% ) { -% $cust_main->set("ship_$_", $cust_main->get($_) ); -% } -% } - +% my $has_ship_address = ''; +% if ( $cgi->param('error') ) { +% $has_ship_address = !$same; +% } elsif ( $cust_main->custnum ) { +% $has_ship_address = $cust_main->has_ship_address; +% } <BR> -<FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT> - -<& cust_main/contact.html, - 'cust_main' => $cust_main, - 'pre' => '', - 'onchange' => 'bill_changed(this)', - 'disabled' => '', - 'ss' => $ss, - 'stateid' => $stateid, - 'same_checked' => $same_checked, #for address2 "Unit #" labeling -&> +<TABLE> <TR> + <TD STYLE="width:650px"> +%#; padding-right:2px; vertical-align:top"> + <FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT> + <TABLE CLASS="fsinnerbox"> + <& cust_main/before_bill_location.html, $cust_main &> + <& /elements/location.html, + object => $cust_main->bill_location, + prefix => 'bill_', + enable_coords => 1, + &> + <& cust_main/after_bill_location.html, $cust_main &> + </TABLE> + </TD> +</TR> +<TR><TD STYLE="height:40px"></TD></TR> +<TR> + <TD STYLE="width:650px"> +%#; padding-left:2px; vertical-align:top"> + <FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT> + <INPUT TYPE="checkbox" + NAME="same" + ID="same" + onclick="samechanged(this)" + onkeyup="samechanged(this)" + VALUE="Y" + <% $has_ship_address ? '' : 'CHECKED' %> + ><% mt('same as billing address') |h %> + <TABLE CLASS="fsinnerbox" ID="table_ship_location"> + <& /elements/location.html, + object => $cust_main->ship_location, + prefix => 'ship_', + enable_censustract => 1, + enable_district => 1, + enable_coords => 1, + &> + </TABLE> + <TABLE CLASS="fsinnerbox" ID="table_ship_location_blank" + STYLE="display:none"> + <TR><TD></TD></TR> + </TABLE> + </TD> +</TR></TABLE> <SCRIPT> -function bill_changed(what) { - if ( what.form.same.checked ) { -% for (qw( last first company address1 address2 city zip latitude longitude coord_auto daytime night fax mobile )) { - what.form.ship_<%$_%>.value = what.form.<%$_%>.value; -% } - - what.form.ship_country.selectedIndex = what.form.country.selectedIndex; - - function fix_ship_city() { - what.form.ship_city_select.selectedIndex = what.form.city_select.selectedIndex; - what.form.ship_city.style.display = what.form.city.style.display; - what.form.ship_city_select.style.display = what.form.city_select.style.display; - } - - function fix_ship_county() { - what.form.ship_county.selectedIndex = what.form.county.selectedIndex; - ship_county_changed(what.form.ship_county, fix_ship_city ); - } - - function fix_ship_state() { - what.form.ship_state.selectedIndex = what.form.state.selectedIndex; - ship_state_changed(what.form.ship_state, fix_ship_county ); - } - - ship_country_changed(what.form.ship_country, fix_ship_state ); - - } -} function samechanged(what) { +%# not display = 'none', because we still want it to take up space +%# document.getElementById('table_ship_location').style.visibility = +%# what.checked ? 'hidden' : 'visible'; + var t1 = document.getElementById('table_ship_location'); + var t2 = document.getElementById('table_ship_location_blank'); if ( what.checked ) { - bill_changed(what); - -% my @fields = qw( last first company address1 address2 city city_select county state zip country latitude longitude daytime night fax mobile ); -% for (@fields) { - what.form.ship_<%$_%>.disabled = true; - what.form.ship_<%$_%>.style.backgroundColor = '#dddddd'; -% } - -% if ( $conf->exists('cust_main-require_address2') ) { - document.getElementById('address2_required').style.visibility = ''; - document.getElementById('address2_label').style.visibility = ''; - document.getElementById('ship_address2_required').style.visibility = 'hidden'; - document.getElementById('ship_address2_label').style.visibility = 'hidden'; -% } - - } else { - -% for (@fields) { - what.form.ship_<%$_%>.disabled = false; - what.form.ship_<%$_%>.style.backgroundColor = '#ffffff'; -% } - -% if ( $conf->exists('cust_main-require_address2') ) { - document.getElementById('address2_required').style.visibility = 'hidden'; - document.getElementById('address2_label').style.visibility = 'hidden'; - document.getElementById('ship_address2_required').style.visibility = ''; - document.getElementById('ship_address2_label').style.visibility = ''; -% } - + t2.style.width = t1.clientWidth + 'px'; + t2.style.height = t1.clientHeight + 'px'; + t1.style.display = 'none'; + t2.style.display = ''; + } + else { + t2.style.display = 'none'; + t1.style.display = ''; } } +samechanged(document.getElementById('same')); </SCRIPT> <BR> -<FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT> - -<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>><% mt('same as billing address') |h %> -<& cust_main/contact.html, - 'cust_main' => $cust_main, - 'pre' => 'ship_', - 'onchange' => '', - 'disabled' => $ship_disabled, - 'style' => \@ship_style -&> <& cust_main/contacts_new.html, 'cust_main' => $cust_main, @@ -229,32 +202,56 @@ my $conf = new FS::Conf; #get record my($custnum, $cust_main, $ss, $stateid, $payinfo, @invoicing_list); -my $same = ''; my $pkgpart_svcpart = ''; #first_pkg my($username, $password, $popnum, $saved_domsvc) = ( '', '', 0, 0 ); #svc_acct my %svc_phone = (); my %svc_dsl = (); my $prospectnum = ''; my $locationnum = ''; +my $same = ''; + if ( $cgi->param('error') ) { + $same = ($cgi->param('same') || '') eq 'Y'; + # false laziness w/ edit/process/cust_main.cgi + my %locations; + for my $pre (qw(bill ship)) { + my %hash; + foreach ( FS::cust_main->location_fields ) { + $hash{$_} = scalar($cgi->param($pre.'_'.$_)); + } + $hash{'custnum'} = $cgi->param('custnum'); + $locations{$pre} = qsearchs('cust_location', \%hash) + || FS::cust_location->new( \%hash ); + } + if ( $same ) { + $locations{ship} = $locations{bill}; + } + $cust_main = new FS::cust_main ( { - map { $_, scalar($cgi->param($_)) } fields('cust_main') + map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main')), + map { ( "ship_$_", '' ) } (FS::cust_main->location_fields) } ); + for my $pre (qw(bill ship)) { + $cust_main->set($pre.'_location', $locations{$pre}); + $cust_main->set($pre.'_locationnum', $locations{$pre}->locationnum); + } + $custnum = $cust_main->custnum; die "access denied" unless $curuser->access_right($custnum ? 'Edit customer' : 'New customer'); @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') ); - $same = $cgi->param('same'); $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid'); $ss = $cust_main->ss; # don't mask an entered value on errors $stateid = $cust_main->stateid; # don't mask an entered value on errors $payinfo = $cust_main->payinfo; # don't mask an entered value on errors + $cust_main->national_id( $cgi->param('national_id1') || $cgi->param('national_id2') ); + $prospectnum = $cgi->param('prospectnum') || ''; $pkgpart_svcpart = $cgi->param('pkgpart_svcpart') || ''; @@ -296,7 +293,7 @@ if ( $cgi->param('error') ) { $cust_main->paycvv($paycvv); } @invoicing_list = $cust_main->invoicing_list; - $ss = $cust_main->masked('ss'); + $ss = $conf->exists('unmask_ss') ? $cust_main->ss : $cust_main->masked('ss'); $stateid = $cust_main->masked('stateid'); $payinfo = $cust_main->paymask; @@ -355,6 +352,20 @@ if ( $cgi->param('error') ) { $svc_dsl{$_} = $qual->$_ foreach qw( phonenum vendor_qual_id ); } + else { + my $countrydefault = $conf->config('countrydefault') || 'US'; + my $statedefault = $conf->config('statedefault') || 'CA'; + $cust_main->set('bill_location', + FS::cust_location->new( + { country => $countrydefault, state => $statedefault } + ) + ); + $cust_main->set('ship_location', + FS::cust_location->new( + { country => $countrydefault, state => $statedefault } + ) + ); + } if ( $cgi->param('lock_pkgpart') =~ /^(\d+)$/ ) { my $pkgpart = $1; @@ -367,7 +378,7 @@ if ( $cgi->param('error') ) { } my %keep = map { $_=>1 } qw( error tagnum lock_agentnum lock_pkgpart ); -$cgi->delete( grep !$keep{$_}, $cgi->param ); +$cgi->delete( grep { !$keep{$_} && $_ !~ /^tax_/ } $cgi->param ); my $title = $custnum ? 'Edit Customer' : 'Add Customer'; $title = mt($title); diff --git a/httemplate/edit/cust_main/after_bill_location.html b/httemplate/edit/cust_main/after_bill_location.html new file mode 100644 index 000000000..2f4c3b51c --- /dev/null +++ b/httemplate/edit/cust_main/after_bill_location.html @@ -0,0 +1,12 @@ +% if ( ! $conf->exists('cust-edit-alt-field-order') ) { + <& phones.html, $cust_main &> + <& fax.html, $cust_main &> +% } else { + <& fax.html, $cust_main &> + <& company.html, $cust_main &> +% } +<& stateid.html, $cust_main &> +<%init> +my $cust_main = shift; +my $conf = FS::Conf->new; +</%init> diff --git a/httemplate/edit/cust_main/before_bill_location.html b/httemplate/edit/cust_main/before_bill_location.html new file mode 100644 index 000000000..973201ecb --- /dev/null +++ b/httemplate/edit/cust_main/before_bill_location.html @@ -0,0 +1,10 @@ +<& name.html, $cust_main &> +% if ( ! $conf->exists('cust-edit-alt-field-order') ) { + <& company.html, $cust_main &> +% } else { + <& phones.html, $cust_main &> +% } +<%init> +my $cust_main = shift; +my $conf = FS::Conf->new; +</%init> diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index 18c7ae9a6..2925ca87c 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -117,6 +117,15 @@ //why? select.selectedIndex = 0; } } + + function tax_changed(what) { + var num = document.getElementById(what.id + '_num'); + if ( what.checked ) { + num.disabled = false; + } else { + num.disabled = true; + } + } </SCRIPT> @@ -437,14 +446,28 @@ % my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); - <TR> - <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD> - </TR> +% if ( $conf->exists('cust_class-tax_exempt') +% || $conf->exists('tax-cust_exempt-groups-require_individual_nums') +% ) +% { + + <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax eq 'Y' ? 'Y' : '' %>"> + +% } else { + + <TR> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt<% @exempt_groups ? ' (all taxes)' : '' %></TD> + </TR> + +% } % foreach my $exempt_group ( @exempt_groups ) { -% #escape $exempt_group for NAME +% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group); +% #escape $exempt_group for NAME etc. +% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group")); <TR> - <TD WIDTH="608" COLSPAN="2"> <INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" VALUE="Y" <% $cust_main->tax_exemption($exempt_group) ? 'CHECKED' : '' %>> Tax Exempt (<% $exempt_group %> taxes)<TD> + <TD> <INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD> + <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD> </TR> % } @@ -458,7 +481,7 @@ ? 'CHECKED' : '' - %>> <% mt('Postal mail invoice') |h %> + %>> <% mt('Postal mail invoices') |h %> </TD> </TR> @@ -470,15 +493,27 @@ ? 'CHECKED' : '' - %>> <% mt('Fax invoice') |h %> + %>> <% mt('Fax invoices') |h %> </TD> </TR> % } -% unless ( $conf->exists('cust-email-high-visibility')) { <TR> + <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoice_email" VALUE="Y" <% + + ( $cust_main->invoice_noemail eq 'Y' ) + ? '' + : 'CHECKED' + + %>> <% mt('Email invoices') |h %> + + </TD> + </TR> + +% unless ( $conf->exists('cust-email-high-visibility')) { + <TR> <TD ALIGN="right" WIDTH="200"> <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum) ? $r : '' %>Email address(es) @@ -487,6 +522,17 @@ </TR> % } +% if ( $conf->exists('cust_main-select-prorate_day') ) { + <TR> + <TD ALIGN="right" WIDTH="200"><% mt('Prorate day (1-28)') |h %> </TD> + <TD> + <INPUT TYPE="text" NAME="prorate_day" VALUE="<% $cust_main->prorate_day %>" SIZE=3 MAXLENGTH=2> + </TD> + </TR> +% } else { + <INPUT TYPE="hidden" NAME="prorate_day" VALUE="<% $cust_main->prorate_day %>"> +% } + <TR> <TD ALIGN="right" WIDTH="200"><% mt('Invoice terms') |h %> </TD> <TD WIDTH="408"> @@ -562,20 +608,25 @@ function toggle(obj) { %my @available_locales = $conf->config('available-locales'); %if ( scalar(@available_locales) ) { -% push @available_locales, ''; -% my %locale_labels = map { -% my %ll; -% my %info = FS::Locales->locale_info($_); -% $ll{$_} = $info{name} . " (" . $info{country} . ")"; -% %ll; -% } FS::Locales->locales; - <& /elements/tr-select.html, - 'label' => emt('Invoicing locale'), - 'field' => 'locale', - 'options' => \@available_locales, - 'labels' => \%locale_labels, - 'curr_value' => $cust_main->locale, - &> +% push @available_locales, '' +% unless $cust_main->locale && $conf->exists('cust_main-require_locale'); +% my %locale_labels = map { +% my %ll; +% my %info = FS::Locales->locale_info($_); +% $ll{$_} = $info{name} . " (" . $info{country} . ")"; +% %ll; +% } FS::Locales->locales; +% +% my $label = ( $conf->exists('cust_main-require_locale') ? $r : '' ). +% emt('Invoicing locale'); + + <& /elements/tr-select.html, + 'label' => $label, + 'field' => 'locale', + 'options' => \@available_locales, + 'labels' => \%locale_labels, + 'curr_value' => $cust_main->locale, + &> % } </TABLE> diff --git a/httemplate/edit/cust_main/birthdate.html b/httemplate/edit/cust_main/birthdate.html index b4e78e3b9..e1adbd3bd 100644 --- a/httemplate/edit/cust_main/birthdate.html +++ b/httemplate/edit/cust_main/birthdate.html @@ -1,16 +1,80 @@ <% ntable("#cccccc", 2) %> - <% include( '/elements/tr-input-date-field.html', - 'birthdate', - $cust_main->birthdate, - 'Date of Birth', - ( $conf->config('date_format') || "%m/%d/%Y" ), - 1 - ) + +% # maybe put after the contact names? + +% my $id_country = $conf->config('national_id-country'); +% if ( $id_country ) { +% if ( $id_country eq 'MY' ) { +% my($old, $nric) = ( '', ''); +% if ( $cust_main->national_id =~ /^\d{6}\-\d{2}\-\d{4}$/ ) { +% $nric = $cust_main->national_id; +% } else { # elsif ( $cust_main->national_id =~ /^\w\d{9}$/ ) { +% $old = $cust_main->national_id; +% #} else { +% # warn "unknown national_id format"; +%# <INPUT TYPE="hidden" NAME="national_id0" VALUE="<% $cust_main->national_id |h %>"> +% } + + <% include( '/elements/tr-input-text.html', + 'field' => 'national_id1', + 'value' => $nric, + 'label' => 'NRIC', + ) + %> + <% include( '/elements/tr-input-text.html', + 'field' => 'national_id2', + 'value' => $old, + 'label' => 'Old IC/Passport', + ) + %> + +% } else { +% warn "unknown national_id-country $id_country"; +% } +% } + +% if ( $conf->exists('cust_main-enable_birthdate') ) { + <% include( '/elements/tr-input-date-field.html', { + 'name' => 'birthdate', + 'value' => $cust_main->birthdate, + 'label' => 'Date of Birth', + 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ), + 'usedatetime' => 1, + 'noinit' => $noinit++, + }) %> +% } + +% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) { + <% include( '/elements/tr-input-date-field.html', { + 'name' => 'spouse_birthdate', + 'value' => $cust_main->spouse_birthdate, + 'label' => 'Spouse Date of Birth', + 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ), + 'usedatetime' => 1, + 'noinit' => $noinit++, + }) + %> +% } + +% if ( $conf->exists('cust_main-enable_anniversary_date') ) { + <% include( '/elements/tr-input-date-field.html', { + 'name' => 'anniversary_date', + 'value' => $cust_main->anniversary_date, + 'label' => 'Anniversary Date', + 'format' => ( $conf->config('date_format') || "%m/%d/%Y" ), + 'usedatetime' => 1, + 'noinit' => $noinit++, + }) + %> +% } + </TABLE> <%init> my( $cust_main, %opt ) = @_; my $conf = new FS::Conf; +my $noinit = 0; + </%init> diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js index 4e1108727..b64f6bdb2 100644 --- a/httemplate/edit/cust_main/bottomfixup.js +++ b/httemplate/edit/cust_main/bottomfixup.js @@ -68,7 +68,9 @@ function copy_payby_fields() { } <% include( '/elements/standardize_locations.js', - 'callback' => 'submit_continue();' + 'callback' => 'submit_continue();', + 'main_prefix' => 'bill_', + 'no_company' => 1, ) %> @@ -94,14 +96,17 @@ function copyelement(from, to) { //alert(from + " (" + from.type + "): " + to.name + " => " + to.value); } +var prefix; + % # the value in 'censustract' is the confirmed censustract; if it's set, % # do nothing here function confirm_censustract() { + prefix = document.getElementById('same').checked ? 'bill_' : 'ship_'; var cf = document.CustomerForm; - if ( cf.elements['censustract'].value == '' ) { + if ( cf.elements[prefix+'censustract'].value == '' ) { var address_info = form_address_info(); - address_info['ship_latitude'] = cf.elements['ship_latitude'].value; - address_info['ship_longitude'] = cf.elements['ship_longitude'].value; + address_info[prefix+'latitude'] = cf.elements[prefix+'latitude'].value; + address_info[prefix+'longitude'] = cf.elements[prefix+'longitude'].value; OLpostAJAX( '<%$p%>/misc/confirm-censustract.html', 'q=' + encodeURIComponent(JSON.stringify(address_info)), @@ -118,8 +123,8 @@ function confirm_censustract() { %# called from confirm-censustract.html function set_censustract(tract, year) { var cf = document.CustomerForm; - cf.elements['censustract'].value = tract; - cf.elements['censusyear'].value = year; + cf.elements[prefix+'censustract'].value = tract; + cf.elements[prefix+'censusyear'].value = year; submit_continue(); } diff --git a/httemplate/edit/cust_main/company.html b/httemplate/edit/cust_main/company.html new file mode 100644 index 000000000..8a6ed0bbf --- /dev/null +++ b/httemplate/edit/cust_main/company.html @@ -0,0 +1,7 @@ +% my $cust_main = shift; +<TR ID="company_row" <% $cust_main->company ? '' : 'STYLE="display:none"' %>> + <TD ALIGN="right"><% mt('Company') |h %></TD> + <TD COLSPAN=6><INPUT TYPE="text" NAME="company" ID="company" SIZE=60 + VALUE="<% $cust_main->company |h %>"> + </TD> +</TR> diff --git a/httemplate/edit/cust_main/fax.html b/httemplate/edit/cust_main/fax.html new file mode 100644 index 000000000..237d4be44 --- /dev/null +++ b/httemplate/edit/cust_main/fax.html @@ -0,0 +1,5 @@ +% my $cust_main = shift; +<TR> + <TD ALIGN="right"><% mt('Fax') |h %></TD> + <TD><INPUT TYPE="text" NAME="fax" VALUE="<% $cust_main->fax %>" SIZE=18></TD> +</TR> diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html new file mode 100644 index 000000000..2641ec930 --- /dev/null +++ b/httemplate/edit/cust_main/name.html @@ -0,0 +1,53 @@ +<%def .namepart> +% my ($field, $value, $label, $extra) = @_; +<TD> + <INPUT TYPE="text" NAME="<% $field %>" VALUE="<% $value |h %>" <%$extra%>> + <BR><FONT SIZE=-1><% mt($label) %></FONT> +</TD> +</%def> + +<TR> + <TH VALIGN="top" ALIGN="right"><%$r%><% mt('Contact name') |h %></TH> + <TD COLSPAN=6> + <TABLE CELLSPACING=0 CELLPADDING=0> + <TR> + <& .namepart, 'last', $cust_main->last, 'Last' &> + <TD VALIGN="top"> , </TD> + <& .namepart, 'first', $cust_main->first, 'First' &> +% if ( $conf->exists('show_ss') ) { + <TD> </TD> + <& .namepart, 'ss', $ss, 'SS#', "SIZE=11" &> +% } else { + <INPUT TYPE="hidden" NAME="ss" VALUE="<% $ss %>"> +% } + </TR> + </TABLE> + </TD> +</TR> + +% if ( $conf->exists('cust-email-high-visibility') ) { +<TR> + <TD ALIGN="right"> + <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum) + ? $r + : '' %>Email address(es) + </TD> + <TD BGCOLOR="#FFFF00"> + <INPUT TYPE="text" NAME="invoicing_list" + VALUE=<% $cust_main->invoicing_list_emailonly_scalar %>> + </TD> +</TR> +% } +<%init> +my $cust_main = shift; +my $agentnum = $cust_main->agentnum if $cust_main->custnum; +my $conf = FS::Conf->new; +my $r = '<font color="#ff0000">*</font> '; +my $ss; + +if ( $cgi->param('error') or $conf->exists('unmask_ss') ) { + $ss = $cust_main->ss; +} else { + $ss = $cust_main->masked('ss'); +} +</%init> diff --git a/httemplate/edit/cust_main/phones.html b/httemplate/edit/cust_main/phones.html new file mode 100644 index 000000000..9b23e0716 --- /dev/null +++ b/httemplate/edit/cust_main/phones.html @@ -0,0 +1,29 @@ +<TR> + <TD VALIGN="top" ALIGN="right"><% mt('Phones') |h %></TD> + <TD COLSPAN=6> + <TABLE CELLSPACING=0 CELLPADDING=0> + <TR> +% foreach my $phone (qw(daytime night mobile)) { + <TD> + <INPUT TYPE="text" + NAME="<% $phone %>" + VALUE="<% $cust_main->get($phone) %>" + SIZE=18 + > + <BR><FONT SIZE=-1><% mt($phone_label{$phone}) |h %></FONT> + </TD> + <TD> </TD> +% } + </TR> + </TABLE> + </TD> +</TR> +<%init> +my $cust_main = shift; +my $conf = FS::Conf->new; +my %phone_label = ( + daytime => 'Day Phone', + night => 'Night Phone', + mobile => 'Mobile', +); +</%init> diff --git a/httemplate/edit/cust_main/stateid.html b/httemplate/edit/cust_main/stateid.html new file mode 100644 index 000000000..2655f5142 --- /dev/null +++ b/httemplate/edit/cust_main/stateid.html @@ -0,0 +1,39 @@ +% if ( $conf->exists('show_stateid') ) { +<TR> + <TD ALIGN="right"><% $stateid_label %></TD> + <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $stateid %>" SIZE=12></TD> + <TD><& /elements/select-state.html, + state => $cust_main->stateid_state, + country => $cust_main->country, # how does this work on new customer? + prefix => 'stateid_', + disable_countyupdate => 1, + &></TD> +</TR> +% } else { +<INPUT TYPE="hidden" NAME="stateid" VALUE="<% $stateid %>"> +<INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $cust_main->stateid_state %>"> +% } + +<%init> +my $cust_main = shift; +my $conf = FS::Conf->new; +my $stateid; +if ( $cgi->param('error') ) { + $stateid = $cust_main->stateid; +} elsif ( $cust_main->custnum ) { + $stateid = $cust_main->masked('stateid'); +} else { + $stateid = ''; +} +$cust_main->set('stateid_state' => $cust_main->state) + unless $cust_main->stateid_state; + +my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/ + ? 'Driver’s License' + : FS::Msgcat::_gettext('stateid') || 'Driver’s License'; + +my $stateid_state_label = + FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/ + ? 'Driver’s License State' + : FS::Msgcat::_gettext('stateid') || 'Driver’s License State'; +</%init> diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html index 7ba167b7f..7ce283c6c 100644 --- a/httemplate/edit/cust_main/top_misc.html +++ b/httemplate/edit/cust_main/top_misc.html @@ -20,27 +20,16 @@ <% $cust_main->residential_commercial eq 'Commercial' ? 'CHECKED' : '' %> ></TD> </TR> - <SCRIPT TYPE="text/javascript"> - function rescom_changed() { - var f = document.CustomerForm; - - if ( f.residential_commercial_Residential.checked ) { - document.getElementById('contacts_div').style.display = 'none'; - } else { // if ( f.residential_commercial_Commercial.checked ) { - document.getElementById('contacts_div').style.display = ''; - } - - if ( f.residential_commercial_Residential.checked && ! f.company.value.length ) { - document.getElementById('company_row').style.display = 'none' - } else { // if ( f.residential_commercial_Commercial.checked ) { + function rescom_changed(what) { + if ( what.checked == (what.value == 'Commercial' ) ) { document.getElementById('company_row').style.display = ''; - } - - if ( f.residential_commercial_Residential.checked && ! f.ship_company.value.length ) { - document.getElementById('ship_company_row').style.display = 'none' - } else { // if ( f.residential_commercial_Commercial.checked ) { - document.getElementById('ship_company_row').style.display = ''; + document.getElementById('contacts_div').style.display = ''; + } else { + if ( document.getElementById('company').value.length == 0 ) { + document.getElementById('company_row').style.display = 'none'; + } + document.getElementById('contacts_div').style.display = 'none'; } } </SCRIPT> diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi index 7a1bb00fa..d4414e44e 100755 --- a/httemplate/edit/cust_pay.cgi +++ b/httemplate/edit/cust_pay.cgi @@ -23,6 +23,7 @@ <% mt('Payment') |h %> <% ntable("#cccccc", 2) %> +% if ( $FS::CurrentUser::CurrentUser->access_right('Backdate payment') ) { <TR> <TD ALIGN="right"><% mt('Date') |h %></TD> <TD COLSPAN=2> @@ -39,6 +40,15 @@ align: "BR" }); </SCRIPT> +% } +% else { +<TR> + <TD ALIGN="right"><% mt('Date') |h %></TD> + <TD COLSPAN=2> + <% time2str($date_format.' %r',$_date) %> + </TD> +</TR> +% } <TR> <TD ALIGN="right"><% mt('Amount') |h %></TD> diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index ba9304066..1ef69fdae 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -141,7 +141,7 @@ my $reason = $cgi->param('reason'); my $link = $cgi->param('popup') ? 'popup' : ''; my @rights = (); -push @rights, 'Post refund' if $payby =~ /^(BILL|CASH)$/; +push @rights, 'Post refund' if $payby =~ /^(BILL|CASH|MCRD)$/; push @rights, 'Post check refund' if $payby eq 'BILL'; push @rights, 'Post cash refund ' if $payby eq 'CASH'; push @rights, 'Refund payment' if $payby =~ /^(CARD|CHEK)$/; diff --git a/httemplate/edit/discount.html b/httemplate/edit/discount.html index b195eb37b..9bcd1e724 100644 --- a/httemplate/edit/discount.html +++ b/httemplate/edit/discount.html @@ -22,6 +22,7 @@ postfix => '<BR><FONT SIZE="-1"><I>(blank for non-expiring discount)</I></FONT>', }, { field => 'setup', type => 'checkbox', value=>'Y', }, + #{ field => 'linked', type => 'checkbox', value=>'Y', }, ], 'labels' => { 'discountnum' => 'Discount #', @@ -32,6 +33,7 @@ 'percent' => 'Percentage ', 'months' => 'Duration (months)', 'setup' => 'Apply to setup fees', + #'linked' => 'Apply to add-on packages', }, 'viewall_dir' => 'browse', 'new_callback' => $new_callback, @@ -114,6 +116,10 @@ my $javascript = <<END; document.getElementById('percent_label').style.visibility = 'hidden'; document.getElementById('percent_input0').style.display = 'none'; document.getElementById('percent_input0').style.visibility = 'hidden'; +// document.getElementById('linked_label').style.display = 'none'; +// document.getElementById('linked_label').style.visibility = 'hidden'; +// document.getElementById('linked').style.display = 'none'; +// document.getElementById('linked').style.visibility = 'hidden'; } else if ( _type == 'Amount' ) { document.getElementById('amount_label').style.display = ''; document.getElementById('amount_label').style.visibility = ''; @@ -123,6 +129,10 @@ my $javascript = <<END; document.getElementById('percent_label').style.visibility = 'hidden'; document.getElementById('percent_input0').style.display = 'none'; document.getElementById('percent_input0').style.visibility = 'hidden'; +// document.getElementById('linked_label').style.display = 'none'; +// document.getElementById('linked_label').style.visibility = 'hidden'; +// document.getElementById('linked').style.display = 'none'; +// document.getElementById('linked').style.visibility = 'hidden'; } else if ( _type == 'Percentage' ) { document.getElementById('amount_label').style.display = 'none'; document.getElementById('amount_label').style.visibility = 'hidden'; @@ -132,6 +142,10 @@ my $javascript = <<END; document.getElementById('percent_label').style.visibility = ''; document.getElementById('percent_input0').style.display = ''; document.getElementById('percent_input0').style.visibility = ''; +// document.getElementById('linked_label').style.display = ''; +// document.getElementById('linked_label').style.visibility = ''; +// document.getElementById('linked').style.display = ''; +// document.getElementById('linked').style.visibility = ''; } } diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html index 69da4db31..0a0916ebc 100644 --- a/httemplate/edit/elements/class_Common.html +++ b/httemplate/edit/elements/class_Common.html @@ -22,21 +22,22 @@ my %opt = @_; my $table = $opt{'table'}; my @category; +my $category_table; unless ( $opt{'nocat'} ) { - ( my $category_table = $table ) =~ s/class/category/ or die; + ( $category_table = $table ) =~ s/class/category/ or die; @category = qsearch($category_table, { 'disabled' => '' }); } my $fields = [ 'classname', (scalar(@category) - ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' } + ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>$category_table, 'name_col'=>'categoryname' } : { field=>'categorynum', type=>'hidden' } ), { field=>'disabled', type=>'checkbox', value=>'Y', }, ]; -push @$fields, $opt{'addl_fields'} if $opt{'addl_fields'}; +push @$fields, @{ $opt{'addl_fields'} } if $opt{'addl_fields'}; my %addl_labels = (); -%addl_labels = %{$opt{'addl_labels'}} if $opt{'addl_labels'}; +%addl_labels = %{ $opt{'addl_labels'} } if $opt{'addl_labels'}; </%init> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 73faad4c0..a24f23805 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -292,6 +292,9 @@ Example: % #& deprecated weird value hashref used only by reason.html % 'value' => $f->{'value'}, % +% #fixed +% 'noescape' => $f->{'noescape'}, +% % #select(-*) % 'options' => $f->{'options'}, % 'labels' => $f->{'labels'}, @@ -308,6 +311,7 @@ Example: % % #umm. for select-agent_types at least % 'disabled' => $f->{'disabled'}, +% 'fixed' => $f->{'fixed'}, % % #any? % 'colspan' => $f->{'colspan'}, @@ -317,7 +321,7 @@ Example: % $include_common{$_} = $f->{$_} foreach grep exists($f->{$_}), % qw( js_only html_only select_only layers_only cell_style ),#selectlayers,? % qw( empty_label ), # select-* -% qw( value_col ), # select-table +% qw( value_col compare_sub ), # select-table % qw( table name_col ), #(select,checkboxes)-table % qw( target_table link_table ), #checkboxes-table % qw( hashref agent_virt agent_null agent_null_right ),#*-table @@ -751,13 +755,15 @@ Example: <BR> - <INPUT TYPE = "submit" - ID = "submit" - VALUE = "<% ( !$clone && $object->$pkey() ) - ? "Apply changes" - : "Add ". ( $opt{'name'} || $opt{'name_singular'} ) - %>" - > +% unless ($opt{'no_submit'}) { + <INPUT TYPE = "submit" + ID = "submit" + VALUE = "<% ( !$clone && $object->$pkey() ) + ? "Apply changes" + : "Add ". ($opt{'name'} || $opt{'name_singular'}) + %>" + > +% } </FORM> diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index 38716f06e..0d9d36c07 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -103,7 +103,15 @@ $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum if $object->svcnum; $f->{'extra_sql'} .= ' ) '; - $f->{'disable_empty'} = $object->svcnum ? 1 : 0, + $f->{'disable_empty'} = $object->svcnum ? 1 : 0; + if ( $f->{'field'} eq 'mac_addr' ) { + $f->{'compare_sub'} = sub { + my($a, $b) = @_; + $a =~ s/[-: ]//g; + $b =~ s/[-: ]//g; + lc($a) eq lc($b); + }; + } } elsif ( $flag eq 'H' ) { $f->{'type'} = 'select-hardware_type'; $f->{'hashref'} = { @@ -127,6 +135,10 @@ $object->set('custnum', $cust_pkg->custnum); } + if ( my $cb = $opt{'svc_field_callback'} ) { + &{ $cb }( $cgi, $object, $f); + } + }, 'html_init' => sub { diff --git a/httemplate/edit/ftp_target.html b/httemplate/edit/ftp_target.html new file mode 100755 index 000000000..aebf9aaed --- /dev/null +++ b/httemplate/edit/ftp_target.html @@ -0,0 +1,46 @@ +<& elements/edit.html, + 'post_url' => popurl(1).'process/ftp_target.html', + 'name' => 'FTP target', + 'table' => 'ftp_target', + 'viewall_url' => "${p}browse/ftp_target.html", + 'labels' => { targetnum => 'Target', + hostname => 'Server', + username => 'Username', + password => 'Password', + path => 'Directory', + port => 'Port', + secure => 'Use SFTP', + handling => 'Special handling', + }, + 'fields' => [ + { field => 'hostname', size => 40 }, + { field => 'port', size => 8 }, + { field => 'secure', type => 'checkbox', value => 'Y' }, + 'username', + 'password', + { field => 'path', size => 40 }, + { field => 'handling', + type => 'select', + options => [ FS::ftp_target->handling_types ], + }, + ], + 'menubar' => \@menubar, + 'edit_callback' => $edit_callback, +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Configuration'); + +my @menubar = ('View all FTP targets' => $p.'browse/ftp_target.html'); +my $edit_callback = sub { + my ($cgi, $object) = @_; + if ( $object->targetnum ) { + push @menubar, 'Delete this target', + $p.'misc/delete-ftp_target.html?'.$object->targetnum; + } +}; + +</%init> diff --git a/httemplate/edit/invoice_template.html b/httemplate/edit/invoice_template.html index 9cec62c86..3553c617b 100644 --- a/httemplate/edit/invoice_template.html +++ b/httemplate/edit/invoice_template.html @@ -8,7 +8,8 @@ <FORM ACTION="process/invoice_template.html" METHOD="POST"> <INPUT TYPE="hidden" NAME="confname" VALUE="<% $confname %>"> -% if ( $type eq 'html' ) { +% #if ( $type eq 'html' ) { +% if ( 0 ) { #this seems to broken, using a text editor for everything for now <% include('/elements/htmlarea.html', 'field' => 'value', diff --git a/httemplate/edit/msg_template.html b/httemplate/edit/msg_template.html index 941554532..115032a07 100644 --- a/httemplate/edit/msg_template.html +++ b/httemplate/edit/msg_template.html @@ -1,14 +1,57 @@ -<% include( 'elements/edit.html', - 'html_init' => '<TABLE id="outerTable"><TR><TD>', - 'body_etc' => $body_etc, - 'name_singular' => 'template', - 'table' => 'msg_template', - 'viewall_dir' => 'browse', - 'agent_virt' => 1, - 'agent_null' => 1, - 'agent_null_right' => ['Edit global templates', 'Configuration'], +<& elements/edit.html, + 'html_init' => '<TABLE id="outerTable"><TR><TD>', + 'body_etc' => $body_etc, + 'name_singular' => 'template', + 'table' => 'msg_template', + 'viewall_dir' => 'browse', + 'agent_virt' => 1, + 'agent_null' => 1, + 'agent_null_right' => [ 'View global templates', 'Edit global templates' ], - 'fields' => [ + 'fields' => \@fields, + 'labels' => { + 'msgnum' => 'Template', + 'agentnum' => 'Agent', + 'msgname' => 'Template name', + 'from_addr' => 'From: ', + 'bcc_addr' => 'Bcc: ', + 'locale' => 'Language', + 'subject' => 'Subject: ', + 'body' => 'Message body', + }, + 'edit_callback' => \&edit_callback, + 'error_callback' => \&edit_callback, + 'html_bottom' => '</DIV>', + 'html_foot' => ( $no_submit ? '' : "</TD>$sidebar</TR></TABLE>" ), + 'no_submit' => $no_submit, +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right([ 'View templates', 'View global templates', + 'Edit templates', 'Edit global templates', + ]); + +my $body_etc = ''; +$body_etc = q!onload="document.getElementById('locale').onchange()"! + if $cgi->param('locale') eq 'new'; + +my $msgnum = $cgi->param('msgnum'); +my $msg_template = $msgnum ? qsearchs('msg_template', {msgnum=>$msgnum}) : ''; + +my $no_submit = 0; +my @fields = (); +if ( $curuser->access_right('Edit global templates') + || ( $curuser->access_right('Edit templates') + && $msg_template + && $msg_template->agentnum + && $curuser->agentnums_href->{$msg_template->agentnum} + ) + ) +{ + push @fields, { field => 'agentnum', type => 'select-agent', }, @@ -25,33 +68,32 @@ type => 'htmlarea', width => 763 }, - ], - 'labels' => { - 'msgnum' => 'Template', - 'agentnum' => 'Agent', - 'msgname' => 'Template name', - 'from_addr' => 'From: ', - 'bcc_addr' => 'Bcc: ', - 'locale' => 'Language', - 'subject' => 'Subject: ', - 'body' => 'Message body', - }, - 'edit_callback' => \&edit_callback, - 'error_callback' => \&edit_callback, - 'html_bottom' => '</DIV>', - 'html_foot' => "</TD>$sidebar</TR></TABLE>", - ) - %> -<%init> + ; +} else { #readonly -die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Edit templates') - || $FS::CurrentUser::CurrentUser->access_right('Edit global templates') - || $FS::CurrentUser::CurrentUser->access_right('Configuration'); + $no_submit = 1; -my $body_etc = ''; -$body_etc = q!onload="document.getElementById('locale').onchange()"! - if $cgi->param('locale') eq 'new'; + push @fields, + { field => 'agentnum', + type => 'select-agent', + fixed => 1, + }, + { field => 'msgname', type => 'fixed', }, + { field => 'from_addr', type => 'fixed', }, + { field => 'bcc_addr', type => 'fixed', }, + { type => 'tablebreak-tabs', + include_opt_callback => \&menubar_opt_callback, + }, + # template_content fields + { field => 'locale', type => 'hidden' }, + { field => 'subject', type => 'fixed', }, + { field => 'body', + type => 'fixed', + noescape => 1, + }, + ; + +} sub new_callback { my ($cgi, $object, $fields_listref, $opt_hashref) = @_; @@ -182,8 +224,18 @@ my %substitutions = ( '$country' => 'Country', '$daytime' => 'Day phone', '$night' => 'Night phone', + '$mobile' => 'Mobile phone', '$fax' => 'Fax', ], + 'service' => [ + '$ship_address1' => 'Address line 1', + '$ship_address2' => 'Address line 2', + '$ship_city' => 'City', + '$ship_county' => 'County', + '$ship_state' => 'State', + '$ship_zip' => 'Zip', + '$ship_country' => 'Country', + ], 'cust_bill' => [ '$invnum' => 'Invoice#', ], @@ -238,15 +290,10 @@ my %substitutions = ( '$error' => 'Decline reason', ], ); -my @c = @{ $substitutions{'contact'} }; -for (my $i=0; $i<scalar(@c); $i += 2) { - $c[$i] =~ s/\$(.*)/\$ship_$1/; -} -$substitutions{'shipping'} = \@c; tie my %sections, 'Tie::IxHash', ( 'contact' => 'Name and contact info (billing)', -'shipping' => 'Name and contact info (shipping)', +'service' => 'Service address', 'cust_main' => 'Customer status and payment info', 'cust_pkg' => 'Package fields', 'cust_bill' => 'Invoice fields', diff --git a/httemplate/edit/nas.html b/httemplate/edit/nas.html index 2e66fc3be..8e6232cdb 100644 --- a/httemplate/edit/nas.html +++ b/httemplate/edit/nas.html @@ -49,7 +49,7 @@ sub html_bottom { 'link_table' => 'export_nas', 'target_table' => 'part_export', 'hashref' => { 'exporttype' => - { op => 'LIKE', value => '%sqlradius' } + { op => 'LIKE', value => '%sqlradius%' } }, 'name_callback' => sub { $_[0]->label }, 'default' => 'yes', diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index 1450ac3b3..0407ee77b 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -13,12 +13,6 @@ </TD> </TR> <TR> - <TD ALIGN="right">Export host</TD> - <TD> - <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>"> - </TD> -</TR> -<TR> <TD ALIGN="right">Export</TD> <TD><% $widget->html %> @@ -63,7 +57,7 @@ my $widget = new HTML::Widgets::SelectLayers( 'options' => \%layers, 'form_name' => 'dummy', 'form_action' => 'process/part_export.cgi', - 'form_text' => [qw( exportnum exportname machine )], + 'form_text' => [qw( exportnum exportname )], # 'form_checkbox' => [qw()], 'html_between' => "</TD></TR></TABLE>\n", 'layer_callback' => sub { @@ -71,9 +65,69 @@ my $widget = new HTML::Widgets::SelectLayers( my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!. ntable("#cccccc",2); - $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. - $exports->{$layer}{notes}. '</TD></TR>' - if $layer; + if ( $layer ) { + $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'. + $exports->{$layer}{notes}. '</TD></TR>'; + + if ( $exports->{$layer}{no_machine} ) { + $html .= '<INPUT TYPE="hidden" NAME="machine" VALUE="">'. + '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">'; + } else { + $html .= '<TR><TD ALIGN="right">Hostname or IP</TD><TD>'; + my $machine = $part_export->machine; + if ( $exports->{$layer}{svc_machine} ) { + my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' ); + my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' ); + my $part_export_machine = ''; + if ( $cgi->param('svc_machine') eq 'Y' + || $machine eq '_SVC_MACHINE' + ) + { + $Y_CHK = 'CHECKED'; + $N_CHK = 'CHECKED'; + $machine_DISABLED = 'DISABLED'; + $pem_DISABLED = ''; + $machine = ''; + $part_export_machine = + $cgi->param('part_export_machine') + || join "\n", + map $_->machine, + grep ! $_->disabled, + $part_export->part_export_machine; + } + my $oc = qq(onChange="${layer}_svc_machine_changed(this)"); + $html .= qq[ + <INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc> + <INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED> + <BR> + <INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc> + Selected in each customer service from these choices + <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA> + + <SCRIPT TYPE="text/javascript"> + function ${layer}_svc_machine_changed (what) { + if ( what.checked ) { + var machine = document.getElementById("${layer}_machine"); + var part_export_machine = document.getElementById("${layer}_part_export_machine"); + if ( what.value == 'Y' ) { + machine.disabled = true; + part_export_machine.disabled = false; + } else if ( what.value == 'N' ) { + machine.disabled = false; + part_export_machine.disabled = true; + } + } + } + </SCRIPT> + ]; + } else { + $html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">). + '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">'; + } + $html .= "</TD></TR>"; + } + + } foreach my $option ( keys %{$exports->{$layer}{options}} ) { my $optinfo = $exports->{$layer}{options}{$option}; @@ -106,7 +160,8 @@ my $widget = new HTML::Widgets::SelectLayers( } if ( $type eq 'select' ) { my $size = defined($optinfo->{size}) ? " SIZE=" . $optinfo->{size} : ''; - my $multi = defined($optinfo->{multi}) ? ' MULTIPLE' : ''; + my $multi = ($optinfo->{multi} || $optinfo->{multiple}) + ? ' MULTIPLE' : ''; $html .= qq!<SELECT NAME="$option"$multi$size>!; my @values = split '\s+', $value if $multi; my @options; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index cd0731370..f3ad8f52d 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -55,6 +55,7 @@ 'svc_dst_pkgpart' => 'Include services of package', 'report_option' => 'Report classes', 'fcc_ds0s' => 'Voice-grade equivalents', + 'fcc_voip_class' => 'Category', }, 'fields' => [ @@ -196,6 +197,9 @@ { type => 'tablebreak-tr-title', value => 'FCC Form 477 information', }, + { field=>'fcc_voip_class', + type=>'select-voip_class', + }, { field=>'fcc_ds0s', type=>'text', size=>6 }, ) : () diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html index daf8773f0..e9fd79452 100755 --- a/httemplate/edit/part_referral.html +++ b/httemplate/edit/part_referral.html @@ -3,9 +3,12 @@ 'table' => 'part_referral', 'fields' => [ 'referral', { field=>'agentnum', type=>'select-agent', }, + { field=>'disabled', type=>'checkbox', value=>'Y' } , ], - 'labels' => { 'referral' => 'Advertising source', + 'labels' => { 'refnum' => 'Ad Source', + 'referral' => 'Advertising source', 'agentnum' => 'Agent', + 'disabled' => 'Disabled', }, 'viewall_dir' => 'browse', ) diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index fae896154..007c24629 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -1,17 +1,27 @@ -<% include('/elements/header.html', "$action Service Definition", +<& /elements/header.html, "$action Service Definition", menubar('View all service definitions' => "${p}browse/part_svc.cgi"), #" onLoad=\"visualize()\"" - ) -%> +&> + +<& /elements/init_overlib.html &> + +<BR> <FORM NAME="dummy"> - Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> -<BR><BR> -Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR> +<FONT CLASS="fsinnerbox-title">Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %></FONT> +<TABLE CLASS="fsinnerbox"> +<TR> + <TD ALIGN="right">Service</TD> + <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD> +<TR> + +<& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &> -Self-service access: -<SELECT NAME="selfservice_access"> +<TR> + <TD ALIGN="right">Self-service access</TD> + <TD> + <SELECT NAME="selfservice_access"> % tie my %selfservice_access, 'Tie::IxHash', #false laziness w/browse/part_svc % '' => 'Yes', % 'hidden' => 'Hidden', @@ -22,12 +32,22 @@ Self-service access: <% $_ eq $hashref->{'selfservice_access'} ? 'SELECTED' : '' %> ><% $selfservice_access{$_} %> % } -</SELECT><BR> + </SELECT> + </TD> +</TR> -<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>> Disable new orders<BR> -<INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> Preserve this service on package cancellation<BR> +<TR> + <TD ALIGN="right">Disable new orders</TD> + <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD> +</TR> +<TR> + <TD ALIGN="right">Preserve this service on package cancellation</TD> + <TD><INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> </TD> +</TR> + +</TABLE> <INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>"> @@ -76,6 +96,18 @@ Self-service access: % ? ( $hashref->{svcdb} ) % : FS::part_svc->svc_tables(); % +% my $help = ''; +% unless ( $hashref->{svcpart} ) { +% $help = ' '. +% include('/elements/popup_link.html', +% 'action' => $p.'docs/part_svc-table.html', +% 'label' => 'help', +% 'actionlabel' => 'Service table help', +% 'width' => 763, +% #'height' => 400, +% ); +% } +% % tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; % my $widget = new HTML::Widgets::SelectLayers( % #'selected_layer' => $p_svcdb, @@ -84,15 +116,16 @@ Self-service access: % 'form_name' => 'dummy', % #'form_action' => 'process/part_svc.cgi', % 'form_action' => 'part_svc.cgi', #self -% 'form_text' => [ qw( svc svcpart ) ], -% 'form_select' => [ 'selfservice_access' ], -% 'form_checkbox' => [ 'disabled', 'preserve' ], +% 'form_elements' => [qw( svc svcpart classnum selfservice_access +% disabled preserve +% )], +% 'html_between' => $help, % 'layer_callback' => sub { % my $layer = shift; % % my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!; % -% $html .= $svcdb_info; +% #$html .= $svcdb_info; % % my $columns = 3; % my $count = 0; @@ -111,12 +144,7 @@ Self-service access: % && qsearchs( 'export_svc', { % exportnum => $part_export->exportnum, % svcpart => $clone || $part_svc->svcpart }); -% $html .= '>'.$part_export->exportnum. ': '; -% $html .= $part_export->exportname . '<DIV ALIGN="right"><FONT SIZE=-1>' -% if ( $part_export->exportname ); -% $html .= $part_export->exporttype. ' to '. $part_export->machine; -% $html .= '</FONT></DIV>' if ( $part_export->exportname ); -% $html .= '</TD>'; +% $html .= '>'. $part_export->label_html. '</TD>'; % $count++; % $html .= '</TR><TR>' unless $count % $columns; % } @@ -267,6 +295,7 @@ Self-service access: % % $html .= include('/elements/select-table.html', % 'element_name' => "${layer}__${field}_classnum", +% 'id' => "${layer}__${field}_classnum", % 'element_etc' => ( $is_inv % ? $disabled % : $nodisplay @@ -349,6 +378,7 @@ Self-service access: % $html .= include('/elements/select-hardware_class.html', % 'curr_value' => $value, % 'element_name' => "${layer}__${field}_classnum", +% 'id' => "${layer}__${field}_classnum", % 'element_etc' => $flag ne 'H' && $nodisplay, % 'empty_label' => 'Select hardware class', % ); @@ -382,7 +412,8 @@ Self-service access: % % $html .= include('/elements/progress-init.html', % $layer, #form name -% [ qw(svc svcpart selfservice_access disabled preserve +% [ qw(svc svcpart classnum selfservice_access +% disabled preserve % exportnum), % @fields ], % 'process/part_svc.cgi', @@ -401,9 +432,8 @@ Self-service access: % % }, % ); -% -% +<BR> Table <% $widget->html %> <% include('/elements/footer.html') %> @@ -451,66 +481,6 @@ my %communigate_fields = ( #'svc_cert' => { map { $_=>1 } qw( ) }, ); -my $svcdb_info = ' -<TABLE> - <TR> - <TH ALIGN="left">Generic</TH> - <TH ALIGN="left">Access</TH> - <TH ALIGN="left">Telephony</TH> -<!-- <TH>Hosting</TH> - <TH>Colocation</TH> ---> - </TR> - <TR> - <TD VALIGN="top"> - <UL STYLE="margin:0"> - <LI><B>svc_acct</B>: Accounts - anything with a username (mailbox, shell, RADIUS, etc.) - <LI><B>svc_hardware</B>: Equipment supplied to customers - <LI><B>svc_external</B>: Externally-tracked service - </UL> - </TD> - <TD VALIGN="top"> - <UL STYLE="margin:0"> - <LI><B>svc_dsl</B>: DSL - <LI><B>svc_broadband</B>: Wireless broadband - <LI><B>svc_dish</B>: DISH Network - </UL> - </TD> - <TD VALIGN="top"> - <UL STYLE="margin:0"> - <LI><B>svc_phone</B>: Customer phone number - <LI><B>svc_pbx</B>: Customer PBX - </UL> - </TD> - </TR> -</TABLE> -<BR> -<TABLE> - <TR> - <TH ALIGN="left">Hosting</TH> - <TH ALIGN="left">Colocation</TH> - </TR> - <TD VALIGN="top"> - <UL STYLE="margin:0"> - <LI><B>svc_domain</B>: Domain - <LI><B>svc_cert</B>: Certificate - <LI><B>svc_forward</B>: Mail forwarding - <LI><B>svc_mailinglist</B>: Mailing list - <LI><B>svc_www</B>: Virtual domain website - </UL> - </TD> - <TD VALIGN="top"> - <UL STYLE="margin:0"> - <LI><B>svc_port</B>: Customer router/switch port - </UL> - </TD> - </TR> -<TABLE> -<!-- <LI>svc_charge - One-time charges (Partially unimplemented) - <LI>svc_wo - Work orders (Partially unimplemented) ---> -'; - my $mod_info = ' For the selected table, you can give fields default or fixed (unchangable) values, or select an inventory class to manually or automatically fill in diff --git a/httemplate/edit/part_svc_class.html b/httemplate/edit/part_svc_class.html new file mode 100644 index 000000000..0d9a00727 --- /dev/null +++ b/httemplate/edit/part_svc_class.html @@ -0,0 +1,6 @@ +<% include( 'elements/class_Common.html', + 'name' => 'Service class', + 'table' => 'part_svc_class', + 'nocat' => 1, + ) +%> diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index cfb86048c..dfe52f109 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -6,11 +6,12 @@ 'field_callback' => $field_callback, 'labels' => { 'gatewaynum' => 'Gateway', + 'gateway_namespace' => 'Gateway type', 'gateway_module' => 'Gateway', 'gateway_username' => 'Username', 'gateway_password' => 'Password', 'gateway_action' => 'Action', - 'gateway_options' => 'Options: (Name/Value pairs, one element per line)', + 'gateway_options' => 'Options (Name/Value pairs, <BR>one element per line)', 'gateway_callback_url' => 'Callback URL', }, ) @@ -18,18 +19,17 @@ <SCRIPT TYPE="text/javascript"> - var gatewayNamespace = new Array; - -% foreach my $module ( sort { lc($a) cmp lc ($b) } keys %modules ) { - gatewayNamespace.push('<% $modules{$module} %>') -% } - - // document.getElementById('gateway_namespace').value = gatewayNamespace[0]; - function setNamespace(what) { - document.getElementById('gateway_namespace').value = - gatewayNamespace[what.selectedIndex]; + var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>; + function changeNamespace(what) { + var ns = what.value; + var select_module = document.getElementById('gateway_module'); + select_module.options.length = 0; + for (var x in modulesForNamespace[ns]) { + var o = document.createElement('option'); + o.value = o.text = modulesForNamespace[ns][x]; + select_module.add(o, null); + } } - </SCRIPT> <%init> @@ -88,7 +88,17 @@ my %modules = ( 'VirtualNet' => 'Business::OnlinePayment', 'WesternACH' => 'Business::OnlinePayment', 'WorldPay' => 'Business::OnlinePayment', -); + + 'KeyBank' => 'Business::BatchPayment', + 'Paymentech' => 'Business::BatchPayment', + 'TD_EFT' => 'Business::BatchPayment', +); + +my %modules_for_namespace; +for (keys %modules) { + $modules_for_namespace{$modules{$_}} ||= []; + push @{ $modules_for_namespace{$modules{$_}} }, $_; +} my @actions = ( 'Normal Authorization', @@ -99,17 +109,23 @@ my @actions = ( my $fields = [ { field => 'gateway_namespace', - type => 'hidden', - curr_value_callback => sub { my($cgi, $object, $fref) = @_; - $modules{$object->gateway_module} - || 'Business::OnlinePayment' - }, + type => 'select', + options => [ qw( + Business::OnlinePayment + Business::BatchPayment + Business::OnlineThirdPartyPayment + ) ], + labels => { + 'Business::OnlinePayment' => 'Direct', + 'Business::BatchPayment' => 'Batch', + 'Business::OnlineThirdPartyPayment' => 'Hosted', + }, + onchange => 'changeNamespace', }, { field => 'gateway_module', type => 'select', options => [ sort { lc($a) cmp lc ($b) } keys %modules ], - onchange => 'setNamespace', }, 'gateway_username', 'gateway_password', @@ -126,6 +142,8 @@ my $fields = [ { field => 'gateway_options', type => 'textarea', + rows => '12', + cols => '40', curr_value_callback => sub { my($cgi, $object, $fref) = @_; join("\r", $object->options ); }, @@ -135,7 +153,7 @@ my $fields = [ my $field_callback = sub { my ($cgi, $object, $field_hashref ) = @_; if ($object->gatewaynum) { - if ( $field_hashref->{field} eq 'gateway_module' ) { + if ( $field_hashref->{field} =~ /gateway_(module|namespace)/ ) { $field_hashref->{type} = 'fixed'; } } diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html index 1bc100e36..c4e3d8ac5 100644 --- a/httemplate/edit/pkg_class.html +++ b/httemplate/edit/pkg_class.html @@ -10,7 +10,7 @@ my $conf = new FS::Conf; my %opt = (); if($conf->exists('cust_main-require_censustract')) { - $opt{'addl_fields'} = qw( fcc_ds0s ); + $opt{'addl_fields'} = [ 'fcc_ds0s' ]; $opt{'addl_labels'} = { 'fcc_ds0s' => 'FCC form 477 voice-grade equivalents' }; } </%init> diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi index f7a1b0801..3f0d6ba1d 100644 --- a/httemplate/edit/prepay_credit.cgi +++ b/httemplate/edit/prepay_credit.cgi @@ -18,13 +18,12 @@ prepaid cards of characters each -<BR>for <SELECT NAME="agentnum"><OPTION>(any agent) -% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) { +<BR>for - <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %> -% } - -</SELECT> +<& /elements/select-agent.html, + 'empty_label' => '(any agent)', + 'curr_value' => $agentnum, +&> <TABLE> <TR><TD>Value: diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi index e776d281c..034c4cc50 100755 --- a/httemplate/edit/process/agent.cgi +++ b/httemplate/edit/process/agent.cgi @@ -1,11 +1,12 @@ <% include( 'elements/process.html', - 'table' => 'agent', - 'viewall_dir' => 'browse', - 'viewall_ext' => 'cgi', - 'process_m2m' => { 'link_table' => 'access_groupagent', - 'target_table' => 'access_group', - }, - 'edit_ext' => 'cgi', + 'table' => 'agent', + 'viewall_dir' => 'browse', + 'viewall_ext' => 'cgi', + 'process_m2m' => { 'link_table' => 'access_groupagent', + 'target_table' => 'access_group', + }, + 'edit_ext' => 'cgi', + 'noerror_callback' => $process_agent_pkg_class, ) %> <%init> @@ -18,4 +19,30 @@ if ( FS::Conf->new->exists('disable_acl_changes') ) { die "shouldn't be reached"; } +my $process_agent_pkg_class = sub { + my( $cgi, $agent ) = @_; + + #surprising amount of false laziness w/ edit/agent.cgi + my @pkg_class = qsearch('pkg_class', { 'disabled'=>'' }); + foreach my $pkg_class ( '', @pkg_class ) { + my %agent_pkg_class = ( 'agentnum' => $agent->agentnum, + 'classnum' => $pkg_class ? $pkg_class->classnum : '' + ); + my $agent_pkg_class = + qsearchs( 'agent_pkg_class', \%agent_pkg_class ) + || new FS::agent_pkg_class \%agent_pkg_class; + + my $param = 'classnum'. $agent_pkg_class{classnum}; + + $agent_pkg_class->commission_percent( $cgi->param($param) ); + + my $method = $agent_pkg_class->agentpkgclassnum ? 'replace' : 'insert'; + + my $error = $agent_pkg_class->$method; + die $error if $error; #XXX push this down into agent.pm w/better/transactional error handling + + } + +}; + </%init> diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi index 790fc8ea4..b9f93db8b 100644 --- a/httemplate/edit/process/cust_location.cgi +++ b/httemplate/edit/process/cust_location.cgi @@ -28,10 +28,12 @@ my $cust_location = qsearchs({ }); die "unknown locationnum $locationnum" unless $cust_location; -my $new = { +my $new = FS::cust_location->new({ + custnum => $cust_location->custnum, + prospectnum => $cust_location->prospectnum, map { $_ => scalar($cgi->param($_)) } qw( address1 address2 city county state zip country ) -}; +}); my $error = $cust_location->move_to($new); diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index 44fbb4f10..31ec4ab12 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -57,17 +57,40 @@ push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX'); $cgi->param('invoicing_list', join(',', @invoicing_list) ); +# is this actually used? if so, we need to clone locations... +# but I can't find anything that sets this parameter to a non-empty value +$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/; +my $duplicate_of = $1; + +my %locations; +for my $pre (qw(bill ship)) { + + my %hash; + foreach ( FS::cust_main->location_fields ) { + $hash{$_} = scalar($cgi->param($pre.'_'.$_)); + } + $hash{'custnum'} = $cgi->param('custnum'); + warn Dumper \%hash if $DEBUG; + # if we can qsearchs it, then it's unchanged, so use that + $locations{$pre} = qsearchs('cust_location', \%hash) + || FS::cust_location->new( \%hash ); + +} + +if ( ($cgi->param('same') || '') eq 'Y' ) { + $locations{ship} = $locations{bill}; +} #create new record object +# but explicitly avoid setting ship_ fields my $new = new FS::cust_main ( { - map { - $_, scalar($cgi->param($_)) - } fields('cust_main') + map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main')), + map { ( "ship_$_", '' ) } (FS::cust_main->location_fields) } ); -$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/; -my $duplicate_of = $1; +$new->invoice_noemail( ($cgi->param('invoice_email') eq 'Y') ? '' : 'Y' ); + if ( $duplicate_of ) { # then negate all changes to the customer; the only change we should # make is to order a package, if requested @@ -76,11 +99,9 @@ if ( $duplicate_of ) { or die "nonexistent existing customer (custnum $duplicate_of)"; } -if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) { - $new->setfield("ship_$_", '') foreach qw( - last first company address1 address2 city county state zip - country daytime night fax - ); +for my $pre (qw(bill ship)) { + $new->set($pre.'_location', $locations{$pre}); + $new->set($pre.'_locationnum', $locations{$pre}->locationnum); } if ( $cgi->param('no_credit_limit') ) { @@ -89,9 +110,16 @@ if ( $cgi->param('no_credit_limit') ) { $new->tagnum( [ $cgi->param('tagnum') ] ); -my %usedatetime = ( 'birthdate' => 1 ); +$error ||= $new->set_national_id_from_cgi( $cgi ); -foreach my $dfield (qw( birthdate signupdate )) { +my %usedatetime = ( 'birthdate' => 1, + 'spouse_birthdate' => 1, + 'anniversary_date' => 1, + ); + +foreach my $dfield (qw( + signupdate birthdate spouse_birthdate anniversary_date +)) { if ( $cgi->param($dfield) && $cgi->param($dfield) =~ /^([ 0-9\-\/]{0,10})$/) { @@ -130,6 +158,7 @@ $new->setfield('paid', $cgi->param('paid') ) my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups; +my %tax_exempt = map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt; #perhaps this stuff should go to cust_main.pm if ( $new->custnum eq '' or $duplicate_of ) { @@ -237,7 +266,7 @@ if ( $new->custnum eq '' or $duplicate_of ) { else { # create the customer $error ||= $new->insert( \%hash, \@invoicing_list, - 'tax_exemption'=> \@tax_exempt, + 'tax_exemption'=> \%tax_exempt, 'prospectnum' => scalar($cgi->param('prospectnum')), ); @@ -256,6 +285,7 @@ if ( $new->custnum eq '' or $duplicate_of ) { my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } ); $error ||= "Old record not found!" unless $old; + if ( length($old->paycvv) && $new->paycvv =~ /^\s*\*+\s*$/ ) { $new->paycvv($old->paycvv); } @@ -294,8 +324,11 @@ if ( $new->custnum eq '' or $duplicate_of ) { local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG; local($FS::Record::DEBUG) = $DEBUG if $DEBUG; + local($Data::Dumper::Sortkeys) = 1; + warn Dumper({ new => $new, old => $old }) if $DEBUG; + $error ||= $new->replace( $old, \@invoicing_list, - 'tax_exemption' => \@tax_exempt, + 'tax_exemption' => \%tax_exempt, ); warn "$me returned from replace" if $DEBUG; diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi index 06f5e64d5..ce0ec3212 100755 --- a/httemplate/edit/process/cust_pay.cgi +++ b/httemplate/edit/process/cust_pay.cgi @@ -39,7 +39,13 @@ $cgi->param('link') =~ /^(custnum|invnum|popup)$/ my $field = my $link = $1; $field = 'custnum' if $field eq 'popup'; -my $_date = parse_datetime($cgi->param('_date')); +my $_date; +if ( $FS::CurrentUser::CurrentUser->access_right('Backdate payment') ) { + $_date = parse_datetime($cgi->param('_date')); +} +else { + $_date = time; +} my $new = new FS::cust_pay ( { $field => $linknum, diff --git a/httemplate/edit/process/cust_pkg_discount.html b/httemplate/edit/process/cust_pkg_discount.html index 6f97a791e..4a71f6975 100644 --- a/httemplate/edit/process/cust_pkg_discount.html +++ b/httemplate/edit/process/cust_pkg_discount.html @@ -39,7 +39,8 @@ my $cust_pkg_discount = new FS::cust_pkg_discount { 'amount' => scalar($cgi->param('discountnum_amount')), 'percent' => scalar($cgi->param('discountnum_percent')), 'months' => scalar($cgi->param('discountnum_months')), - 'setup' => scalar($cgi->param('discountnum_setup')), + 'setup' => scalar($cgi->param('discountnum_setup')), + #'linked' => scalar($cgi->param('discountnum_linked')), #'disabled' => $self->discountnum_disabled, }; my $error = $cust_pkg_discount->insert; diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index f4cce6535..bde40727a 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -31,7 +31,7 @@ my $link = $cgi->param('popup') ? 'popup' : ''; my $payby = $cgi->param('payby'); my @rights = (); -push @rights, 'Post refund' if $payby =~ /^(BILL|CASH)$/; +push @rights, 'Post refund' if $payby =~ /^(BILL|CASH|MCRD)$/; push @rights, 'Post check refund' if $payby eq 'BILL'; push @rights, 'Post cash refund ' if $payby eq 'CASH'; push @rights, 'Refund payment' if $payby =~ /^(CARD|CHEK)$/; diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 12b3bd94b..2d39e9dce 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -250,8 +250,6 @@ foreach my $value ( @values ) { } - $error ||= $new->check; - my @args = (); if ( !$error && $opt{'args_callback'} ) { @args = &{ $opt{'args_callback'} }( $cgi, $new ); diff --git a/httemplate/edit/process/ftp_target.html b/httemplate/edit/process/ftp_target.html new file mode 100644 index 000000000..35f56c490 --- /dev/null +++ b/httemplate/edit/process/ftp_target.html @@ -0,0 +1,12 @@ +<& elements/process.html, + 'table' => 'ftp_target', + 'viewall_dir' => 'browse', + 'agent_null' => 1, +&> +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/process/msg_template.html b/httemplate/edit/process/msg_template.html index 47fe978a8..b19f5c542 100644 --- a/httemplate/edit/process/msg_template.html +++ b/httemplate/edit/process/msg_template.html @@ -9,9 +9,7 @@ %> <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Edit templates') - || $FS::CurrentUser::CurrentUser->access_right('Edit global templates') - || $FS::CurrentUser::CurrentUser->access_right('Configuration'); + unless $FS::CurrentUser::CurrentUser->access_right(['Edit templates','Edit global templates']); sub precheck_callback { my $cgi = shift; diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi index 21150ef67..6432d6b15 100644 --- a/httemplate/edit/process/part_export.cgi +++ b/httemplate/edit/process/part_export.cgi @@ -28,6 +28,11 @@ my $new = new FS::part_export ( { } fields('part_export') } ); +if ( $cgi->param('svc_machine') eq 'Y' ) { + $new->machine('_SVC_MACHINE'); + $new->part_export_machine_textarea( $cgi->param('part_export_machine') ); +} + my $error; if ( $exportnum ) { #warn $old; diff --git a/httemplate/edit/process/part_svc_class.html b/httemplate/edit/process/part_svc_class.html new file mode 100644 index 000000000..16165dd18 --- /dev/null +++ b/httemplate/edit/process/part_svc_class.html @@ -0,0 +1,11 @@ +<% include( 'elements/process.html', + 'table' => 'part_svc_class', + 'viewall_dir' => 'browse', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index fab85252b..c5eee0cb8 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -2,19 +2,24 @@ % $cgi->param('error', $error); <% $cgi->redirect(popurl(3). 'misc/order_pkg.html?'. $cgi->query_string ) %> %} else { -% my $frag = "cust_pkg". $cust_pkg->pkgnum; % my $show = $curuser->default_customer_view =~ /^(jumbo|packages)$/ % ? '' % : ';show=packages'; -% my $redir_url = popurl(3) -% ."view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag"; +% +% my $redir_url = popurl(3); +% if ( $svcpart ) { # for going straight to service provisining after ordering +% $redir_url .= 'edit/'.$part_svc->svcdb.'.cgi?'. +% 'pkgnum='.$cust_pkg->pkgnum. ";svcpart=$svcpart"; +% $redir_url .= ";qualnum=$qualnum" if $qualnum; +% } elsif ( $quotationnum ) { +% $redir_url .= "view/quotation.html?quotationnum=$quotationnum"; +% } else { +% my $custnum = $cust_main->custnum; +% my $frag = "cust_pkg". $cust_pkg->pkgnum; +% $redir_url .= +% "view/cust_main.cgi?custnum=$custnum$show;fragment=$frag#$frag"; +% } % -% # for going right to a provision service after ordering a package -% if ( $svcpart ) { -% $redir_url = popurl(3)."edit/".$part_svc->svcdb.".cgi?". -% "pkgnum=".$cust_pkg->pkgnum. ";svcpart=$svcpart"; -% $redir_url .= ";qualnum=$qualnum" if $qualnum; -% } <% header('Package ordered') %> <SCRIPT TYPE="text/javascript"> // XXX fancy ajax rebuild table at some point, but a page reload will do for now @@ -33,21 +38,35 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('Order customer package'); -#untaint custnum (probably not necessary, searching for it is escape enough) -$cgi->param('custnum') =~ /^(\d+)$/ - or die 'illegal custnum '. $cgi->param('custnum'); -my $custnum = $1; -my $cust_main = qsearchs({ - 'table' => 'cust_main', - 'hashref' => { 'custnum' => $custnum }, - 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -}); -die 'unknown custnum' unless $cust_main; +my $cust_main; +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + my $custnum = $1; + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); +} + +my $prospect_main; +if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { + my $prospectnum = $1; + $prospect_main = qsearchs({ + 'table' => 'prospect_main', + 'hashref' => { 'prospectnum' => $prospectnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); +} + +die 'no custnum or prospectnum' unless $cust_main || $prospect_main; #probably not necessary, taken care of by cust_pkg::check $cgi->param('pkgpart') =~ /^(\d+)$/ or die 'illegal pkgpart '. $cgi->param('pkgpart'); my $pkgpart = $1; +$cgi->param('quantity') =~ /^(\d+)$/ + or die 'illegal quantity '. $cgi->param('quantity'); +my $quantity = $1; $cgi->param('refnum') =~ /^(\d*)$/ or die 'illegal refnum '. $cgi->param('refnum'); my $refnum = $1; @@ -69,46 +88,70 @@ if ( $cgi->param('svcpart') ) { } my $qualnum = ''; -if ( $cgi->param('qualnum') ) { - $cgi->param('qualnum') =~ /^(\d+)$/ or die 'illegal qualnum'; +if ( $cgi->param('qualnum') =~ /^(\d+)$/ ) { $qualnum = $1; } +my $quotationnum = ''; +if ( $cgi->param('quotationnum') =~ /^(\d+)$/ ) { + $quotationnum = $1; +} +# verify this quotation is visible to this user +my $cust_pkg = ''; +my $quotation_pkg = ''; +my $error = ''; -my $cust_pkg = new FS::cust_pkg { - 'custnum' => $custnum, - 'pkgpart' => $pkgpart, - 'start_date' => ( scalar($cgi->param('start_date')) - ? parse_datetime($cgi->param('start_date')) - : '' - ), - 'no_auto' => scalar($cgi->param('no_auto')), - 'refnum' => $refnum, - 'locationnum' => $locationnum, - 'discountnum' => $discountnum, - #for the create a new discount case - 'discountnum__type' => scalar($cgi->param('discountnum__type')), - 'discountnum_amount' => scalar($cgi->param('discountnum_amount')), - 'discountnum_percent' => scalar($cgi->param('discountnum_percent')), - 'discountnum_months' => scalar($cgi->param('discountnum_months')), - 'discountnum_setup' => scalar($cgi->param('discountnum_setup')), - 'contract_end' => ( scalar($cgi->param('contract_end')) - ? parse_datetime($cgi->param('contract_end')) - : '' - ), - 'waive_setup' => ( $cgi->param('waive_setup') eq 'Y' ? 'Y' : '' ), -}; - -my %opt = ( 'cust_pkg' => $cust_pkg ); - -if ( $locationnum == -1 ) { - my $cust_location = new FS::cust_location { - map { $_ => scalar($cgi->param($_)) } - qw( custnum address1 address2 city county state zip country geocode ) - }; - $opt{'cust_location'} = $cust_location; -} +my %hash = ( + 'pkgpart' => $pkgpart, + 'quantity' => $quantity, + 'start_date' => ( scalar($cgi->param('start_date')) + ? parse_datetime($cgi->param('start_date')) + : '' + ), + 'refnum' => $refnum, + 'locationnum' => $locationnum, + 'discountnum' => $discountnum, + #for the create a new discount case + 'discountnum__type' => scalar($cgi->param('discountnum__type')), + 'discountnum_amount' => scalar($cgi->param('discountnum_amount')), + 'discountnum_percent' => scalar($cgi->param('discountnum_percent')), + 'discountnum_months' => scalar($cgi->param('discountnum_months')), + 'discountnum_setup' => scalar($cgi->param('discountnum_setup')), + 'contract_end' => ( scalar($cgi->param('contract_end')) + ? parse_datetime($cgi->param('contract_end')) + : '' + ), + 'waive_setup' => ( $cgi->param('waive_setup') eq 'Y' ? 'Y' : '' ), +); +$hash{'custnum'} = $cust_main->custnum if $cust_main; + +if ( $quotationnum ) { + + $quotation_pkg = new FS::quotation_pkg \%hash; + $quotation_pkg->quotationnum($quotationnum); + $quotation_pkg->prospectnum($prospect_main->prospectnum) if $prospect_main; -my $error = $cust_main->order_pkg( \%opt ); + #XXX handle new location + $error = $quotation_pkg->insert; + +} else { + + $cust_pkg = new FS::cust_pkg \%hash; + + $cust_pkg->no_auto( scalar($cgi->param('no_auto')) ); + + my %opt = ( 'cust_pkg' => $cust_pkg ); + + if ( $locationnum == -1 ) { + my $cust_location = new FS::cust_location { + map { $_ => scalar($cgi->param($_)) } + qw( custnum address1 address2 city county state zip country geocode ) + }; + $opt{'cust_location'} = $cust_location; + } + + $error = $cust_main->order_pkg( \%opt ); + +} </%init> diff --git a/httemplate/edit/process/quotation.html b/httemplate/edit/process/quotation.html new file mode 100644 index 000000000..a69566581 --- /dev/null +++ b/httemplate/edit/process/quotation.html @@ -0,0 +1,11 @@ +<% include( 'elements/process.html', + 'table' => 'quotation', + 'redirect' => popurl(3).'view/quotation.html?', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); + +</%init> diff --git a/httemplate/edit/process/sales.cgi b/httemplate/edit/process/sales.cgi new file mode 100644 index 000000000..edef4d65c --- /dev/null +++ b/httemplate/edit/process/sales.cgi @@ -0,0 +1,23 @@ +<% include( 'elements/process.html', + 'table' => 'sales', + 'viewall_dir' => 'browse', + 'viewall_ext' => 'cgi', + 'debug' => '1', + 'process_m2m' => { 'link_table' => 'access_groupsales', + 'target_table' => 'access_group', + }, + 'edit_ext' => 'cgi', + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +if ( FS::Conf->new->exists('disable_acl_changes') ) { + errorpage('ACL changes disabled in public demo.'); + die "shouldn't be reached"; +} + +</%init> + diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi index a7d5136fb..41aca65ee 100755 --- a/httemplate/edit/process/svc_acct.cgi +++ b/httemplate/edit/process/svc_acct.cgi @@ -56,13 +56,14 @@ my $new = new FS::svc_acct ( \%hash ); my $error = ''; +my $part_svc = $svcnum ? + $old->part_svc : + qsearchs( 'part_svc', + { 'svcpart' => $cgi->param('svcpart') } + ); + # google captcha auth if ( $cgi->param('captcha_response') ) { - my $part_svc = $svcnum ? - $old->part_svc : - qsearchs( 'part_svc', - { 'svcpart' => $cgi->param('svcpart') } - ); my ($export) = $part_svc->part_export('acct_google'); if ( $export and ! $export->captcha_auth($cgi->param('captcha_response')) ) { @@ -79,6 +80,18 @@ if ( $cgi->param('clear_password') eq '*HIDDEN*' } if ( ! $error ) { + + my $export_info = FS::part_export::export_info(); + + my @svc_export_machine = + map FS::svc_export_machine->new({ + 'svcnum' => $svcnum, + 'exportnum' => $_->exportnum, + 'machinenum' => scalar($cgi->param('exportnum'.$_->exportnum.'machinenum')), + }), + grep { $_->machine eq '_SVC_MACHINE' } + $part_svc->part_export; + if ( $svcnum ) { foreach ( grep { $old->$_ != $new->$_ } qw( seconds upbytes downbytes totalbytes ) @@ -92,9 +105,9 @@ if ( ! $error ) { $error ||= $new->set_usage(\%hash); #unoverlimit and trigger radius changes last; #once is enough } - $error ||= $new->replace($old); + $error ||= $new->replace($old, 'child_objects'=>\@svc_export_machine); } else { - $error ||= $new->insert; + $error ||= $new->insert('child_objects'=>\@svc_export_machine); $svcnum = $new->svcnum; } } diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi index 90eab4aad..25644e547 100644 --- a/httemplate/edit/process/svc_broadband.cgi +++ b/httemplate/edit/process/svc_broadband.cgi @@ -1,11 +1,10 @@ <& elements/svc_Common.html, - table => 'svc_broadband', - fields => [ fields('svc_broadband'), fields('nas'), 'usergroup' ], + table => 'svc_broadband', + fields => [ fields('svc_broadband'), fields('nas'), 'usergroup' ], precheck_callback => \&precheck, &> <%init> -# for historical reasons, process_m2m for usergroup tables is done -# in the svc_x::insert/replace/delete methods, not here + my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html index c8c8e98e1..d3985410b 100644 --- a/httemplate/edit/prospect_main.html +++ b/httemplate/edit/prospect_main.html @@ -36,6 +36,9 @@ 'empty_label' => 'No address', 'disable_empty' => $conf->exists('prospect_main-location_required'), 'alt_format' => $conf->exists('prospect_main-alt_address_format'), + 'include_opt_callback' => sub { + 'prospect_main' => shift + }, }, ], 'new_callback' => $new_callback, diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html index 27841063f..1d9647f2f 100644 --- a/httemplate/edit/quick-charge.html +++ b/httemplate/edit/quick-charge.html @@ -11,13 +11,30 @@ <SCRIPT TYPE="text/javascript"> -function enable_quick_charge () { +function enable_quick_charge (e) { + if ( document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) { document.QuickChargeForm.submit.disabled = false; } else { document.QuickChargeForm.submit.disabled = true; } + +% if ( $curuser->option('disable_enter_submit_onetimecharge') ) { + + var key; + if (window.event) + key = window.event.keyCode; //IE + else + + key = e.which; //firefox, others + + return (key != 13); + +% } else { + return true; +% } + } function validate_quick_charge () { @@ -76,7 +93,12 @@ function bill_now_changed (what) { </SCRIPT> -<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" ID="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();"> +<FORM ACTION = "process/quick-charge.cgi" + NAME = "QuickChargeForm" + ID = "QuickChargeForm" + METHOD = "POST" + onSubmit = "document.QuickChargeForm.submit.disabled=true; return validate_quick_charge();" +> <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> @@ -85,7 +107,13 @@ function bill_now_changed (what) { <TR> <TD ALIGN="right"><% mt('Amount') |h %> </TD> <TD> - <% $money_char %><INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()"> + <% $money_char %><INPUT TYPE = "text" + NAME = "amount" + SIZE = 6 + VALUE = "<% $amount %>" + onChange = "return enable_quick_charge(event)" + onKeyPress = "return enable_quick_charge(event)" + > </TD> </TR> @@ -93,7 +121,11 @@ function bill_now_changed (what) { <TR> <TD ALIGN="right"><% mt('Quantity') |h %> </TD> <TD> - <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>"> + <INPUT TYPE = "text" + NAME = "quantity" + SIZE = 4 + VALUE = "<% $quantity %>" + onKeyPress = "return enable_quick_charge(event)"> </TD> </TR> % } @@ -107,6 +139,7 @@ function bill_now_changed (what) { NAME = "bill_now" VALUE = "1" <% $cgi->param('bill_now') ? 'CHECKED' : '' %> + onClick = "bill_now_changed(this);" onChange = "bill_now_changed(this);" > <% mt('with terms') |h %> @@ -127,7 +160,11 @@ function bill_now_changed (what) { SIZE = 32 ID = "start_date_text" VALUE = "<% $start_date %>" - <% $cgi->param('bill_now') ? 'STYLE = "background-color:#dddddd" DISABLED' : '' %> + onKeyPress="return enable_quick_charge(event)" + <% $cgi->param('bill_now') + ? 'STYLE = "background-color:#dddddd" DISABLED' + : '' + %> > <IMG SRC = "<%$fsurl%>images/calendar.png" ID = "start_date_button" @@ -173,7 +210,14 @@ function bill_now_changed (what) { <TR> <TD ALIGN="right"><% mt('Description') |h %> </TD> <TD> - <INPUT TYPE="text" NAME="pkg" SIZE="50" MAXLENGTH="50" VALUE="<% $pkg %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge()"> + <INPUT TYPE = "text" + NAME = "pkg" + SIZE = "50" + MAXLENGTH = "50" + VALUE = "<% $pkg %>" + onChange = "return enable_quick_charge(event)" + onKeyPress = "return enable_quick_charge(event)" + > </TD> </TR> @@ -191,7 +235,15 @@ function bill_now_changed (what) { <TR> <TD></TD> <TD> - <INPUT TYPE="text" NAME="description<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $param->{"description$row"} |h %>" rownum="<% $row %>" onkeyup = "possiblyAddRow;" > + <INPUT TYPE = "text" + NAME = "description<% $row %>" + SIZE = "60" + MAXLENGTH = "65" + VALUE = "<% $param->{"description$row"} |h %>" + rownum = "<% $row %>" + onKeyPress = "return enable_quick_charge(event)" + onKeyUp = "return possiblyAddRow(event)" + > </TD> </TR> % } @@ -210,10 +262,26 @@ function bill_now_changed (what) { var rownum = <% $row %>; - function possiblyAddRow() { + function possiblyAddRow(e) { + if ( ( rownum - this.getAttribute('rownum') ) == 1 ) { addRow(); } + +% if ( $curuser->option('disable_enter_submit_onetimecharge') ) { + + var key; + if (window.event) + key = window.event.keyCode; //IE + else + key = e.which; //firefox, others + + return (key != 13); + +% } else { + return true; +% } + } function addRow() { @@ -228,14 +296,16 @@ function bill_now_changed (what) { var description_cell = document.createElement('TD'); - var description_input = document.createElement('INPUT'); - description_input.setAttribute('name', 'description'+rownum); - description_input.setAttribute('id', 'description'+rownum); - description_input.setAttribute('size', 60); - description_input.setAttribute('maxLength', 65); - description_input.setAttribute('rownum', rownum); - description_input.onkeyup = possiblyAddRow; - description_cell.appendChild(description_input); + //var description_input = document.createElement('INPUT'); + var di = document.createElement('INPUT'); + di.setAttribute('name', 'description'+rownum); + di.setAttribute('id', 'description'+rownum); + di.setAttribute('size', 60); + di.setAttribute('maxLength', 65); + di.setAttribute('rownum', rownum); + di.onkeyup = possiblyAddRow; + di.onkeypress = enable_quick_charge; + description_cell.appendChild(di); row.appendChild(description_cell); @@ -251,8 +321,10 @@ function bill_now_changed (what) { </HTML> <%init> +my $curuser = $FS::CurrentUser::CurrentUser; + die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('One-time charge'); + unless $curuser->access_right('One-time charge'); my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; diff --git a/httemplate/edit/quotation.html b/httemplate/edit/quotation.html new file mode 100644 index 000000000..8b6062355 --- /dev/null +++ b/httemplate/edit/quotation.html @@ -0,0 +1,30 @@ +<% include( 'elements/edit.html', + 'name' => 'Quotation', + 'table' => 'quotation', + 'labels' => { + 'quotationnum' => 'Quotation number', + 'prospectnum' => 'Prospect', + 'custnum' => 'Customer', + '_date' => 'Date', + 'disabled' => 'Disabled', + }, + 'fields' => [ + { field=>'prospectnum', type=>'fixed-prospect_main' }, + { field=>'custnum', type=>'fixed-cust_main' }, + { field=>'_date', type=>'fixed-date' }, + { field=>'disabled', type=>'checkbox', value=>'Y'}, + ], + #XXX some way to disable the "view all" + 'new_callback' => sub { my( $cgi, $quotation) = @_; + $quotation->$_( $cgi->param($_) ) + foreach qw( prospectnum custnum ); + $quotation->_date(time); + }, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); + +</%init> diff --git a/httemplate/edit/radius_group.html b/httemplate/edit/radius_group.html index 025561159..d3ef40c5d 100644 --- a/httemplate/edit/radius_group.html +++ b/httemplate/edit/radius_group.html @@ -7,7 +7,10 @@ 'description' => 'Description', 'attrnum' => 'Attribute', 'priority' => 'Priority', + 'speed_down' => 'Download speed', + 'speed_up' => 'Upload speed', }, + 'viewall_dir' => 'browse', 'menubar' => \@menubar, 'edit_callback' => $edit_callback, 'error_callback' => $edit_callback, @@ -27,6 +30,16 @@ 'size' => 2, 'colspan' => 6, # just to not interfere with radius_attr columns }, + { 'field' => 'speed_down', + 'type' => 'text', + 'size' => 8, + 'colspan' => 6, + }, + { 'field' => 'speed_up', + 'type' => 'text', + 'size' => 8, + 'colspan' => 6, + }, { 'field' => 'attrnum', 'type' => 'radius_attr', diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html index 620a2ea15..78d044755 100644 --- a/httemplate/edit/reason.html +++ b/httemplate/edit/reason.html @@ -1,50 +1,78 @@ -% -% $cgi->param('class') =~ /^(\w)$/ or die "illegal class"; -% my $class=$1; -% -% my $classname = $FS::reason_type::class_name{$class}; -% -% my (@types) = qsearch( 'reason_type', { 'class' => $class } ); -% -% unless (scalar(@types)) { -% print $cgi->redirect( "reason_type.html?class=$class" ); -% } -<% include( 'elements/edit.html', - 'name' => ucfirst($classname) . ' Reason', - 'table' => 'reason', - 'labels' => { - 'reasonnum' => ucfirst($classname) . ' Reason', - 'reason_type' => ucfirst($classname) . ' Reason type', - 'reason' => ucfirst($classname) . ' Reason', - 'disabled' => 'Disabled', - 'class' => '', - }, - 'fields' => [ - { 'field' => 'reason_type', - 'type' => 'select', - #XXX use something more sane than a hashref - #then fix tr-select.html - 'value' => { 'vcolumn' => 'typenum', - 'ccolumn' => 'type', - 'values' => \@types, - }, - }, - 'reason', - { 'field' => 'class', - 'type' => 'hidden', - 'value' => $class, - }, - { 'field' => 'disabled', - 'type' => 'checkbox', - 'value' => 'Y' - }, - ], - 'viewall_url' => $p . "browse/reason.html?class=$class", - ) -%> +<& elements/edit.html, + 'menubar'=> [ "View all $classname Reasons" => + $p.'browse/reason.html?class='.$class, + "View $classname Reason Types" => + $p.'browse/reason_type.html?class='.$class, + ], + 'name' => ucfirst($classname) . ' Reason', + + 'table' => 'reason', + 'labels' => { + 'reasonnum' => $classname . ' Reason', + 'reason_type' => $classname . ' Reason type', + 'reason' => $classname . ' Reason', + 'disabled' => 'Disabled', + 'class' => '', + 'unsuspend_pkgpart' => 'Unsuspension fee', + 'unsuspend_hold' => 'Delay until next bill', + }, + 'fields' => \@fields, +&> <%init> die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +$cgi->param('class') =~ /^(\w)$/ or die "illegal class"; +my $class=$1; + +my $classname = ucfirst($FS::reason_type::class_name{$class}); + +my (@types) = qsearch( 'reason_type', { 'class' => $class } ); + +unless (scalar(@types)) { + print $cgi->redirect( "reason_type.html?class=$class" ); +} + +my @fields = ( + { 'field' => 'reason_type', + 'type' => 'select-table', + 'table' => 'reason_type', + 'name_col' => 'type', + 'value_col' => 'typenum', + 'hashref' => { 'class' => $class }, + 'disable_empty' => 1, +# #then fix tr-select.html +# +# 'value' => { 'vcolumn' => 'typenum', +# 'ccolumn' => 'type', +# 'values' => \@types, +# }, +# # that wasn't so hard...did this do something else that I'm missing? + }, + 'reason', + { 'field' => 'class', + 'type' => 'hidden', + 'value' => $class, + }, + { 'field' => 'disabled', + 'type' => 'checkbox', + 'value' => 'Y' + }, +); + +push @fields, + { 'field' => 'unsuspend_pkgpart', + 'type' => 'select-part_pkg', + 'hashref' => { 'disabled' => '', + 'freq' => 0 }, # one-time charges only + }, + { 'field' => 'unsuspend_hold', + 'type' => 'checkbox', + 'value' => 'Y', + }, + if ( $class eq 'S' ); + + + </%init> diff --git a/httemplate/edit/sales.cgi b/httemplate/edit/sales.cgi new file mode 100755 index 000000000..3497de505 --- /dev/null +++ b/httemplate/edit/sales.cgi @@ -0,0 +1,79 @@ +<% include("/elements/header.html","$action Sales Person", menubar( + 'View all sales people' => $p. 'browse/sales.cgi', +)) %> + +<% include('/elements/error.html') %> + +<FORM METHOD = POST + ACTION = "<%popurl(1)%>process/sales.cgi" +> + +<INPUT TYPE="hidden" NAME="salesnum" VALUE="<% $sales->salesnum %>"> +Sales #<% $sales->salesnum ? $sales->salesnum : "(NEW)" %> + +<% &ntable("#cccccc", 2, '') %> + + <TR> + <TH ALIGN="right">Sales</TH> + <TD><INPUT TYPE="text" NAME="salesperson" SIZE=32 VALUE="<% $sales->salesperson %>"></TD> + </TR> + + <TR> + <TD ALIGN="right"><% emt('Agent') %></TD> + <TD> + <& /elements/select-agent.html, + 'curr_value' => $sales->salesnum, + 'disable_empty' => 1, + &> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Disable</TD> + <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $sales->disabled eq 'Y' ? ' CHECKED' : '' %>></TD> + </TR> + + <TR> + <TD ALIGN="right">Access Groups</TD> + <TD><% include('/elements/checkboxes-table.html', + 'source_obj' => $sales, + 'link_table' => 'access_groupsales', + 'target_table' => 'access_group', + 'name_col' => 'groupname', + 'target_link' => $p. 'edit/access_group.html?', + ) + %> + </TD> + </TR> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% $sales->salesnum ? "Apply changes" : "Add sales" %>"> + +</FORM> + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $sales; +if ( $cgi->param('error') ) { + $sales = new FS::sales ( { + map { $_, scalar($cgi->param($_)) } fields('sales') + } ); +} elsif ( $cgi->keywords ) { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $sales = qsearchs( 'sales', { 'salesnum' => $1 } ); +} else { #adding + $sales = new FS::sales {}; +} +my $action = $sales->salesnum ? 'Edit' : 'Add'; + +my $conf = new FS::Conf; + +</%init> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 38567ef67..142c11150 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -173,6 +173,12 @@ function randomPass() { <INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>"> %} +<& /elements/tr-svc_export_machine.html, + 'svc' => $svc_acct, + 'part_svc' => $part_svc, + 'cgi' => $cgi, +&> + % #uid/gid % foreach my $xid (qw( uid gid )) { % diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index b266928a1..0d4b9897b 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -3,7 +3,7 @@ 'name' => 'broadband service', 'table' => 'svc_broadband', 'fields' => \@fields, - 'field_callback' => $field_callback, + 'svc_field_callback' => $svc_field_callback, 'svc_new_callback' => $svc_edit_callback, 'svc_edit_callback' => $svc_edit_callback, 'svc_error_callback' => $svc_edit_callback, @@ -161,20 +161,14 @@ my $svc_edit_callback = sub { } }; -my $field_callback = sub { +my $svc_field_callback = sub { my ($cgi, $object, $fieldref) = @_; my $columndef = $part_svc->part_svc_column($fieldref->{'field'}); - if ($columndef->columnflag eq 'F') { - $fieldref->{'type'} = length($columndef->columnvalue) - ? 'fixed' - : 'hidden'; - $fieldref->{'value'} = $columndef->columnvalue; + if ($fieldref->{field} eq 'usergroup' && $columndef->columnflag eq 'F') { - if ( $fieldref->{field} eq 'usergroup' ) { - $fieldref->{'formatted_value'} = - [ $object->radius_groups('long_description') ]; - } + $fieldref->{'formatted_value'} = + [ $object->radius_groups('long_description') ]; } }; diff --git a/httemplate/edit/svc_dsl.cgi b/httemplate/edit/svc_dsl.cgi index 1aeadb376..36345b9c5 100644 --- a/httemplate/edit/svc_dsl.cgi +++ b/httemplate/edit/svc_dsl.cgi @@ -52,12 +52,24 @@ my $edit_cb = sub { elsif($export->exporttype eq 'ikano') { @fields = ( 'password', 'monitored', ); - foreach my $hf ( keys %$ti_fields ) { - push @fields, { - field => $hf, - type => 'hidden', - value => $svc_x->$hf, - } unless ( $hf eq 'password' || $hf eq 'monitored' ); + if ( $svc_x->vendor_qual_id ) { + push @fields, { field => 'vendor_qual_id', + type => 'hidden', + value => $svc_x->vendor_qual_id, + }; + } else { + push @fields, 'vendor_qual_id'; + } + + foreach my $hf ( + grep { $_ !~ /^(password|monitored|vendor_qual_id)$/ } + keys %$ti_fields + ) { + push @fields, { + field => $hf, + type => 'hidden', + value => $svc_x->$hf, + }; } } # else add any other export-specific stuff here diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html index 5a0f2a8ce..03b488e86 100644 --- a/httemplate/edit/tower.html +++ b/httemplate/edit/tower.html @@ -12,12 +12,16 @@ m2_label => 'Sector', m2_error_callback => $m2_error_callback, }, + 'latitude', + 'longitude', ], labels => { 'towernum' => 'Tower', 'towername' => 'Name', 'sectornum' => 'Sector', 'disabled' => 'Disabled', 'default_ip_addr' => 'Tower IP address', + 'latitude' => 'Latitude', + 'longitude' => 'Longitude', }, &> <%init> diff --git a/httemplate/elements/coord-links.html b/httemplate/elements/coord-links.html index 3fd3ff696..02a224a00 100644 --- a/httemplate/elements/coord-links.html +++ b/httemplate/elements/coord-links.html @@ -3,7 +3,7 @@ <& /elements/popup_link.html, 'action' => $p. 'view/map.html?'. $query, 'label' => mt('map'), - 'actionlabel' => $name, + 'actionlabel' => $js_name, 'width' => 763, 'height' => 575, #'color' @@ -12,8 +12,8 @@ % if ( $origin ) { <& /elements/popup_link.html, 'action' => $p. "view/directions.html?origin=$origin;". $query, - 'label' => mt('dir'), - 'actionlabel' => $name, + 'label' => mt('directions'), + 'actionlabel' => $js_name, 'width' => 763, 'height' => 575, &> @@ -29,6 +29,12 @@ my $query = 'name='. uri_escape_utf8($name). ';lat='. $latitude. ';lon='. $longitude; +my $js_name = $name; +$js_name =~ s/[<>"]/ /g; +$m->interp->apply_escapes($js_name, 'js_string'); +$js_name =~ s/^'//; +$js_name =~ s/'$//; + my $origin; #for directions link if ( $agentnum =~ /^\d+$/ ) { diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html index a517ece2a..75e682d92 100644 --- a/httemplate/elements/customer-table.html +++ b/httemplate/elements/customer-table.html @@ -41,6 +41,8 @@ Example: <SCRIPT TYPE="text/javascript"> + var num_open_invoices = new Array; + function clearhint_invnum() { if ( this.value == 'Not found' || this.value == 'Multiple' ) { @@ -90,7 +92,7 @@ Example: customer_select.style.display = 'none'; return false; - } else if ( customerArray.length == 5 ) { + } else if ( customerArray.length == 6 ) { custnum_obj.value = customerArray[0]; custnum_obj.style.color = '#000000'; @@ -99,6 +101,7 @@ Example: update_balance_text(searchrow, customerArray[2]); update_status_text( searchrow, customerArray[3]); update_status_color(searchrow, '#'+customerArray[4]); + update_num_open(searchrow, customerArray[5]); customer.style.display = ''; customer_select.style.display = 'none'; @@ -140,6 +143,7 @@ Example: update_balance_text(searchrow, ''); update_status_text(searchrow, ''); update_status_color(searchrow, '#000000'); + update_num_open(searchrow, 0); function search_invnum_update(customers) { @@ -175,15 +179,16 @@ Example: if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) { <% $opt{prefix} %>addRow(); } - var customer = document.getElementById('customer'+searchrow); - customer.value = 'searching...'; - customer.disabled = true; - customer.style.color = '#000000'; - customer.style.backgroundColor = '#dddddd'; + + var customer_obj = document.getElementById('customer'+searchrow); + customer_obj.value = 'searching...'; + customer_obj.disabled = true; + customer_obj.style.color = '#000000'; + customer_obj.style.backgroundColor = '#dddddd'; var customer_select = document.getElementById('cust_select'+searchrow); - customer.style.display = ''; + customer_obj.style.display = ''; customer_select.style.display = 'none'; var invnum = document.getElementById('invnum'+searchrow); @@ -192,15 +197,48 @@ Example: update_balance_text(searchrow, ''); update_status_text( searchrow, ''); update_status_color(searchrow, '#000000'); + update_num_open(searchrow, 0); function search_custnum_update(customers) { - var customerArray = eval('(' + customers + ')') || []; - update_customer(searchrow, customerArray); + var customerArrayArray = eval('(' + customers + ')') || []; + + if ( customerArrayArray.length == 0 ) { + + update_customer(searchrow, []); + + } else if ( customerArrayArray.length == 1 ) { + update_customer(searchrow, customerArrayArray[0]); % if ( $opt{custnum_update_callback} ) { - <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') + <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') % } + + } else { + + custnum_obj.value = 'Multiple'; // or something + custnum_obj.style.color = '#ff0000'; + + //blank the current list + customer_select.options.length = 0; + + opt(customer_select, '', 'Multiple customers match "' + custnum + '" - select one', '#ff0000'); + //add the multiple customers + for ( var s = 0; s < customerArrayArray.length; s++ ) { + opt(customer_select, + JSON.stringify(customerArrayArray[s]), + customerArrayArray[s][1], + '#000000'); + } + + opt(customer_select, 'cancel', '(Edit search string)', '#000000'); + + customer_obj.style.display = 'none'; + + customer_select.style.display = ''; + + } + } custnum_search(custnum, search_custnum_update ); @@ -243,9 +281,13 @@ Example: custnum_obj.disabled = false; custnum_obj.style.backgroundColor = '#ffffff'; - if ( customerArrayArray.length == 1 ) { + if ( customerArrayArray.length == 0 ) { + + update_customer(searchrow, []); + + } else if ( customerArrayArray.length == 1 ) { - update_customer(customerArrayArray[1]); + update_customer(searchrow, customerArrayArray[0]); % if ( $opt{custnum_update_callback} ) { <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>') % } @@ -337,6 +379,9 @@ Example: document.getElementById('balance'+rownum+'_text').innerHTML = newval; } + function update_num_open(rownum, newval) { + num_open_invoices[rownum] = newval; + } </SCRIPT> @@ -356,7 +401,7 @@ Example: % my $row = 0; % for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { - <TR> + <TR id="row<%$row%>" rownum="<%$row%>"> <TD> <INPUT TYPE = "text" NAME = "invnum<% $row %>" @@ -458,19 +503,24 @@ Example: % my $color = $opt{color}->[$col]; % my $font = $color ? qq(<FONT COLOR="$color">) : ''; % my $onchange = ''; -% if ( $opt{footer}->[$col] eq '_TOTAL' ) { +% if ( $opt{onchange}->[$col] ) { +% $onchange = 'onchange="'.$opt{onchange}->[$col].'"'; +% } +% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) { % $total[$col] += $value; % $onchange = $opt{prefix}. "calc_total$col();"; % $onchange = qq(onchange="$onchange" onkeyup="$onchange"); % } <TD ALIGN="<% $align %>"> -% if (! $types->[$col] || $types->[$col] eq 'text') { - <INPUT TYPE = "text" +% my $type = $types->[$col] || 'text'; +% if ($type eq 'text' or $type eq 'checkbox') { + <INPUT TYPE = "<% $type %>" NAME = "<% $name %>" ID = "<% $name %>" SIZE = "<% $size %>" STYLE = "text-align: <% $align %>;" VALUE = "<% $value %>" + rownum = "<% $row %>" <% $onchange %> > % } elsif ($types->[$col] eq 'immutable') { @@ -485,7 +535,7 @@ Example: </TR> % } -<TR> +<TR id="row_total"> <TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL"> Total <% $row ? $row-1 : 0 %> <% PL($opt{name_singular} || 'customer', ( $row ? $row-1 : 0 ) ) %> @@ -559,7 +609,8 @@ Example: var table = document.getElementById('<% $opt{prefix} %>OneTrueTable'); var tablebody = table.getElementsByTagName('tbody').item(0); - var row = table.insertRow(rownum+1); + var row = table.insertRow(table.rows.length - 1); + row.setAttribute('id', 'row'+rownum); var invnum_cell = document.createElement('TD'); @@ -676,7 +727,7 @@ Example: % } else { % $value = $param->{"$field$row"}; % } - var my_text = document.createTextNode('<% $value %>'); + var my_text = document.createTextNode(<% $value |js_string %>); my_cell.appendChild(my_text); % } @@ -686,10 +737,17 @@ Example: my_input.setAttribute('id', '<% $name %>'+<% $opt{prefix} %>rownum); my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>'; my_input.setAttribute('size', <% $sizes->[$col] || 10 %>); -% if ($types->[$col] eq 'immutable') { + my_input.setAttribute('rownum', <% $opt{prefix} %>rownum); +% if ( $types->[$col] eq 'immutable' ) { my_input.setAttribute('type', 'hidden'); % } -% if ( $opt{footer}->[$col] eq '_TOTAL' ) { +% elsif ( $types->[$col] eq 'checkbox' ) { + my_input.setAttribute('type', 'checkbox'); +% } +% if ( $opt{onchange}->[$col] ) { + my_input.onchange = <% $opt{onchange}->[$col] %>; +% } +% elsif ( $opt{footer}->[$col] eq '_TOTAL' ) { my_input.onchange = <% $opt{prefix} %>calc_total<%$col%>; my_input.onkeyup = <% $opt{prefix} %>calc_total<%$col%>; % } @@ -713,6 +771,11 @@ Example: + ' <% PL($opt{name_singular} || 'customer') %>'; } +% if ( $opt{add_row_callback} ) { + <% $opt{add_row_callback} %>(<% $opt{prefix} %>rownum, + '<% $opt{prefix} %>'); +% } + <% $opt{prefix} %>rownum++; } diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html index 72f596f4a..c6362e0c9 100644 --- a/httemplate/elements/dashboard-toplist.html +++ b/httemplate/elements/dashboard-toplist.html @@ -32,18 +32,21 @@ </FONT> </TD> -% foreach my $priority ( @custom_priorities, '' ) { -% my $num = -% FS::TicketSystem->num_customer_tickets($custnum,$priority); -% my $ahref = ''; -% $ahref= '<A HREF="'. -% FS::TicketSystem->href_customer_tickets($custnum,$priority). -% '">' -% if $num; - +% foreach my $priority ( @custom_priorities ) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> - <% $ahref.$num %></A> - </TD> +% my $num = $num_tickets_by_priority{$priority}->{$custnum}; +% if ( $num ) { + <A HREF="<% + FS::TicketSystem->href_customer_tickets($custnum,$priority) + %>"><% $num %></A> +% if ( $priority && +% exists($num_tickets_by_priority{''}{$custnum}) ) { +% # decrement the customer's total by the number in +% # this priority bin +% $num_tickets_by_priority{''}{$custnum} -= $num; +% } +% } + </TD> % } </TR> @@ -77,7 +80,7 @@ <TH CLASS="grid" BGCOLOR="#cccccc"><% $line %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Lint') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"></TH> -% foreach my $priority ( @custom_priorities, '' ) { +% foreach my $priority ( @custom_priorities ) { <TH CLASS="grid" BGCOLOR="#cccccc"> <% $priority || '<i>(none)</i>'%> </TH> @@ -105,11 +108,83 @@ my $conf = new FS::Conf; #false laziness w/httemplate/search/cust_main.cgi... care if # custom_priority_field becomes anything but a local hack... + my @custom_priorities = (); -if ( $conf->config('ticket_system-custom_priority_field') +my $custom_priority_field = $conf->config('ticket_system-custom_priority_field'); +if ( $custom_priority_field && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { @custom_priorities = $conf->config('ticket_system-custom_priority_field-values'); } - +push @custom_priorities, ''; + +my %num_tickets_by_priority = map { $_ => {} } @custom_priorities; +# "optimization" (i.e. "terrible hack") to avoid constructing +# (@custom_priorities) x (cust_main) queries with a bazillion +# joins each just to count tickets +if ( $FS::TicketSystem::system eq 'RT_Internal' + and $conf->config('dashboard-toplist') ) +{ + my $text = (driver_name =~ /^Pg/) ? 'text' : 'char'; + # The RT API does not play nicely with aggregate queries, + # so we're going to go around it. + my $sql; + # optimization to keep this from taking a million years + my $cust_tickets = + "SELECT custnum, Tickets.Id, Tickets.Queue + FROM cust_main + JOIN Links ON ( + Links.Target = 'freeside://freeside/cust_main/' || CAST(cust_main.custnum AS $text) + AND Links.Base LIKE '%rt://%/ticket/%' + AND Links.Type = 'MemberOf' + ) JOIN Tickets ON (Links.LocalBase = Tickets.Id) + UNION + SELECT custnum, Tickets.Id, Tickets.Queue + FROM cust_pkg JOIN cust_svc USING (pkgnum) + JOIN Links ON ( + Links.Target = 'freeside://freeside/cust_svc/' || CAST(cust_svc.svcnum AS $text) + AND Links.Base LIKE '%rt://%/ticket/%' + AND Links.Type = 'MemberOf' + ) JOIN Tickets ON (Links.LocalBase = Tickets.Id) + "; + + if ( $custom_priority_field ) { + $sql = + "SELECT cust_tickets.custnum AS custnum, + ObjectCustomFieldValues.Content as priority, + COUNT(DISTINCT cust_tickets.Id) AS num_tickets + FROM ($cust_tickets) AS cust_tickets + LEFT JOIN ObjectCustomFields ON ( + ObjectCustomFields.ObjectId = '0' OR + ObjectCustomFields.ObjectId = cust_tickets.Queue + ) + LEFT JOIN CustomFields ON ( + ObjectCustomFields.CustomField = CustomFields.Id AND + CustomFields.Name = '$custom_priority_field' + ) + LEFT JOIN ObjectCustomFieldValues ON ( + ObjectCustomFieldValues.CustomField = CustomFields.Id AND + ObjectCustomFieldValues.ObjectType = 'RT::Ticket' AND + ObjectCustomFieldValues.Disabled = '0' AND + ObjectCustomFieldValues.ObjectId = cust_tickets.Id + ) + GROUP BY cust_tickets.custnum, ObjectCustomFieldValues.Content"; + #warn $sql."\n"; + } else { # no custom_priority_field + $sql = + "SELECT cust_tickets.custnum, + '' as priority, + COUNT(DISTINCT cust_tickets.Id) AS num_tickets + FROM ($cust_tickets) AS cust_tickets + GROUP BY cust_tickets.custnum"; + } + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + while ( my $row = $sth->fetchrow_hashref ) { + #warn to_json($row)."\n"; + $num_tickets_by_priority{ $row->{priority} }->{ $row->{custnum} } = + $row->{num_tickets}; + } +} +#warn Dumper \%num_tickets_by_priority; </%init> diff --git a/httemplate/elements/form-create_ticket.html b/httemplate/elements/form-create_ticket.html new file mode 100644 index 000000000..362e82397 --- /dev/null +++ b/httemplate/elements/form-create_ticket.html @@ -0,0 +1,38 @@ +<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline"> +<SCRIPT TYPE="text/javascript"> +function updateTicketLink() { + var link = document.getElementById('CreateTicketLink'); + var selector = document.getElementById('Queue') + link.href = "<% $new_base.'?'. + join(';', map( + { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"} + keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value; +} +</SCRIPT> +<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A> +<A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A> + <% mt('in queue') |h %> +%# fetch list of queues in which the user can create tickets +% my %queues = FS::TicketSystem->queues('', 'CreateTicket'); +% if( $conf->exists('ticket_system-force_default_queueid') ) { +<B><% $queues{$new_param{'Queue'}} %></B> +<INPUT TYPE="hidden" NAME="Queue" VALUE="<% $new_param{'Queue'} %>"> +% } +% else { +<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()"> +% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) { + <OPTION VALUE="<% $queueid %>" + <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %> + ><% $queues{$queueid} |h %> +% } +</SELECT> +<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT> +% } +</FORM> +<%init> +my %opt = @_; +my $conf = new FS::Conf; +my $object = $opt{'object'}; # must be a cust_main, cust_svc, or svc_... +my ($new_base, %new_param) = FS::TicketSystem->href_params_new_ticket($object); +my $new_link = FS::TicketSystem->href_new_ticket($object); +</%init> diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index c291e1e33..82eb9b562 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -108,8 +108,8 @@ a.fstab { -moz-border-radius-topright:8px; -webkit-border-radius-topleft:8px; -webkit-border-radius-topright:8px; - border-radius-topleft:8px; - border-radius-topright:8px; + border-top-left-radius:8px; + border-top-right-radius:8px; /*font-weight:bold;*/ /*padding-left:12px; padding-right:12px;*/ @@ -141,8 +141,8 @@ a.fstabselected { -moz-border-radius-topright:8px; -webkit-border-radius-topleft:8px; -webkit-border-radius-topright:8px; - border-radius-topleft:8px; - border-radius-topright:8px; + border-top-left-radius:8px; + border-top-right-radius:8px; /*font-weight:bold;*/ /*padding-left:12px; padding-right:12px;*/ @@ -175,8 +175,8 @@ div.fstabcontainer { -moz-border-radius-bottomright:8px; -webkit-border-radius-bottomleft:8px; -webkit-border-radius-bottomright:8px; - border-radius-bottomleft:8px; - border-radius-bottomright:8px; + border-bottom-left-radius:8px; + border-bottom-right-radius:8px; -moz-box-shadow: #666666 1px 1px 2px; -webkit-box-shadow: #666666 1px 1px 2px; box-shadow: #666666 1px 1px 2px; @@ -206,8 +206,8 @@ div.fstabcontainer { -moz-border-radius-bottomright:8px; -webkit-border-radius-bottomleft:8px; -webkit-border-radius-bottomright:8px; - border-radius-bottomleft:8px; - border-radius-bottomright:8px; + border-bottom-left-radius:8px; + border-bottom-right-radius:8px; -moz-box-shadow: #666666 1px 1px 2px; -webkit-box-shadow: #666666 1px 1px 2px; box-shadow: #666666 1px 1px 2px; @@ -238,8 +238,8 @@ div.fstabcontainer { -moz-border-radius-topright:8px; -webkit-border-radius-topleft:8px; -webkit-border-radius-topright:8px; - border-radius-topleft:8px; - border-radius-topright:8px; + border-top-left-radius:8px; + border-top-right-radius:8px; -moz-box-shadow: 1px 0px 1px #999999; -webkit-box-shadow: 1px 0px 1px #999999; box-shadow: 1px 0px 1px #999999; diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index d0ab3055d..c6ad3c387 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -42,6 +42,12 @@ Example: <% include('init_overlib.html') |n %> <% include('rs_init_object.html') |n %> <% include('logout.html') |n %> +% my $timeout = $conf->config('logout-timeout'); +% if ( $timeout && $timeout =~ /^\s*\d+\s*$/ ) { + <script type="text/javascript"> + setTimeout('logout()', <% 60000 * $timeout %>); + </script> +% } <% $head |n %> diff --git a/httemplate/elements/init_overlib.html b/httemplate/elements/init_overlib.html index d27ca3bda..986adec40 100644 --- a/httemplate/elements/init_overlib.html +++ b/httemplate/elements/init_overlib.html @@ -1,9 +1,16 @@ % for my $file (@files) { <SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/<%$file%>.js"></SCRIPT> % } +<%shared> +my $initialized = 0; #won't work if component is "preloaded"... so don't do that +</%shared> <%init> -my @files = map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) ); -push @files, map { "${_}contentmws" } qw( iframe ajax ); +my @files = (); +if ( ! $initialized ) { + push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) ); + push @files, map { "${_}contentmws" } qw( iframe ajax ); + $initialized++; +} </%init> diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html index f3640510a..4df218b43 100644 --- a/httemplate/elements/location.html +++ b/httemplate/elements/location.html @@ -3,16 +3,18 @@ Example: include( '/elements/location.html', - 'object' => $cust_main, # or $cust_location - 'prefix' => $pre, #only for cust_main objects + 'object' => $cust_location + 'prefix' => $pre, # prefixed to form field names 'onchange' => $javascript, - 'disabled' => $disabled, - 'same_checked' => $same_checked, 'geocode' => $geocode, #passed through 'censustract' => $censustract, #passed through 'no_asterisks' => 0, #set true to disable the red asterisks next #to required fields 'address1_label' => 'Address', #label for address + 'enable_coords' => 1, #show latitude/longitude fields + 'enable_district' => 1, #show tax district field + 'enable_censustract' => 1, #show censustract field + ) </%doc> @@ -40,12 +42,12 @@ Example: % } <TR> - <<%$th%> ALIGN="right"><%$r%><% $opt{'address1_label'} || emt('Address') %></<%$th%>> + <<%$th%> STYLE="width:16ex" ALIGN="right"><%$r%><% $opt{'address1_label'} || emt('Address') %></<%$th%>> <TD COLSPAN=7> <INPUT TYPE = "text" NAME = "<%$pre%>address1" ID = "<%$pre%>address1" - VALUE = "<% $object->get($pre.'address1') |h %>" + VALUE = "<% $object->get('address1') |h %>" SIZE = 54 onChange = "<% $onchange %>" <% $disabled %> @@ -62,7 +64,7 @@ Example: <INPUT TYPE = "text" NAME = "<%$pre%>address2" ID = "<%$pre%>address2" - VALUE = "<% $object->get($pre.'address2') |h %>" + VALUE = "<% $object->get('address2') |h %>" SIZE = 54 onChange = "<% $onchange %>" <% $disabled %> @@ -75,7 +77,7 @@ Example: <INPUT TYPE = "hidden" NAME = "<%$pre%>address2" - VALUE = "<% $object->get($pre.'address2') |h %>" + VALUE = "<% $object->get('address2') |h %>" > <TR> @@ -83,7 +85,7 @@ Example: <TD COLSPAN=7> % my $location_type = scalar($cgi->param('location_type')) -% || $object->get($pre.'location_type'); +% || $object->get('location_type'); % #my $location_number = scalar($cgi->param('location_number')) % # || $object->get($pre.'location_number'); % @@ -130,7 +132,7 @@ Example: <INPUT TYPE="text" NAME = "location_number" ID = "location_number" - VALUE = "<% scalar($cgi->param('location_number')) || $object->get($pre.'location_number') |h %>" + VALUE = "<% scalar($cgi->param('location_number')) || $object->get('location_number') |h %>" SIZE = "5" <% $disabled || ($location_type ? '' : 'DISABLED') %> <% $style %> @@ -161,7 +163,7 @@ Example: <INPUT TYPE = "text" NAME = "<%$pre%>zip" ID = "<%$pre%>zip" - VALUE = "<% $object->get($pre.'zip') |h %>" + VALUE = "<% $object->get('zip') |h %>" SIZE = 11 onChange = "<% $onchange %>" <% $disabled %> @@ -175,13 +177,14 @@ Example: <TD COLSPAN=6><% include('/elements/select-country.html', %select_hash ) %></TD> </TR> +% if ( $opt{enable_coords} ) { <TR> <TD ALIGN="right"><% mt('Latitude') |h %></TH> <TD COLSPAN=7> <INPUT TYPE = "text" NAME = "<%$pre%>latitude" ID = "<%$pre%>latitude" - VALUE = "<% $object->get($pre.'latitude') |h %>" + VALUE = "<% $object->get('latitude') |h %>" <% $disabled %> <% $style %> > @@ -189,37 +192,48 @@ Example: <INPUT TYPE = "text" NAME = "<%$pre%>longitude" ID = "<%$pre%>longitude" - VALUE = "<% $object->get($pre.'longitude') |h %>" + VALUE = "<% $object->get('longitude') |h %>" <% $disabled %> <% $style %> > </TD> </TR> -<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->get($pre.'coord_auto') %>"> - -% if ( !$pre ) { - <INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>"> % } else { -% if ( $pre eq 'ship_' && $conf->exists('cust_main-require_censustract') ) { - <TR><<%$th%> ALIGN="right">Census tract<BR>(automatic)</<%$th%>> - <TD> - <INPUT TYPE="text" NAME="enter_censustract" VALUE="<% $opt{censustract} %>"> - <INPUT TYPE="hidden" NAME="censusyear" VALUE="<% $object->get('censusyear') %>"> - <INPUT TYPE="hidden" NAME="censustract" VALUE=""> - </TD> - </TR> +% foreach (qw(latitude longitude)) { +<INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $object->get($_) |h%>"> +% } +% } +<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>"> + +<INPUT TYPE="hidden" NAME="<%$pre%>geocode" VALUE="<% $object->geocode %>"> +<INPUT TYPE="hidden" NAME="<%$pre%>censustract" VALUE="<% $object->censustract %>"> +<INPUT TYPE="hidden" NAME="<%$pre%>censusyear" VALUE="<% $object->censusyear %>"> +% if ( $opt{enable_censustract} ) { +<TR> + <TD ALIGN="right">Census tract</TD> + <TD COLSPAN=8> + <INPUT TYPE="text" SIZE=15 + NAME="<%$pre%>enter_censustract" + VALUE="<% $object->censustract %>"> + <% '(automatic)' %> + </TD> +</TR> +% } +% if ( $conf->config('tax_district_method') ) { +% if ( $opt{enable_district} ) { + <TR> + <TD ALIGN="right">Tax district</TD> + <TD COLSPAN=8> + <INPUT TYPE="text" SIZE=15 + NAME="<%$pre%>district" + VALUE="<% $object->district %>"> + <% '(automatic)' %> + </TD> + </TR> % } else { - <INPUT TYPE="hidden" NAME="censustract" VALUE="<% $opt{censustract} %>"> -% } -% if ( $conf->config('tax_district_method') or $object->get('district') ) { - <TR> - <<%$th%> ALIGN="right">Tax district<BR>(automatic)</<%$th%>> - <TD> - <INPUT TYPE="text" NAME="district" VALUE="<%$object->get('district')%>"> - </TD> - </TR> + <INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>"> % } -% } +% } %# For address standardization: %# keep a clean copy of the address so we know if we need @@ -244,16 +258,13 @@ my $conf = new FS::Conf; my $r = $opt{'no_asterisks'} ? '' : qq!<font color="#ff0000">*</font> !; -#false laziness with ship state my $countrydefault = $conf->config('countrydefault') || 'US'; -$object->set($pre.'country', $countrydefault ) - unless $object->get($pre.'country'); - -my $statedefault = $conf->config('statedefault') +my $statedefault = $conf->config('statedefault') || ($countrydefault eq 'US' ? 'CA' : ''); -$object->set($pre.'state', $statedefault ) - unless $object->get($pre.'state') - || $object->get($pre.'country') ne $countrydefault; +$object ||= FS::cust_location->new({ + 'country' => $countrydefault, + 'state' => $statedefault, +}); my $alt_err = ($opt{'alt_format'} && !$disabled) ? $object->alternize : ''; @@ -266,8 +277,8 @@ push @address2_label_style, 'visibility:hidden' || ! $conf->exists('cust_main-require_address2') || ( !$pre && !$opt{'same_checked'} ); -my @counties = counties( $object->get($pre.'state'), - $object->get($pre.'country'), +my @counties = counties( $object->get('state'), + $object->get('country'), ); my @county_style = (); push @county_style, 'display:none' # 'visibility:hidden' @@ -287,10 +298,10 @@ my $county_style = : ''; my %select_hash = ( - 'city' => $object->get($pre.'city'), - 'county' => $object->get($pre.'county'), - 'state' => $object->get($pre.'state'), - 'country' => $object->get($pre.'country'), + 'city' => $object->get('city'), + 'county' => $object->get('county'), + 'state' => $object->get('state'), + 'country' => $object->get('country'), 'prefix' => $pre, 'onchange' => $onchange, 'disabled' => $disabled, diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index b1cbebf34..b2141e991 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -94,6 +94,11 @@ tie my %report_prospects, 'Tie::IxHash', 'Advanced prospect reports' => [ $fsurl. 'search/report_prospect_main.html', '' ], ; +tie my %report_quotations, 'Tie::IxHash', + 'List quotations' => [ $fsurl. 'search/quotation.html', '' ], + 'Advanced quotation reports' => [ $fsurl. 'search/report_quotation.html', '' ], +; + tie my %report_customers_lists, 'Tie::IxHash', 'by customer number' => [ $fsurl. 'search/cust_main.cgi?browse=custnum', '' ], 'by last name' => [ $fsurl. 'search/cust_main.cgi?browse=last', '' ], @@ -110,8 +115,7 @@ $report_customers{'List customers'} = [ \%report_customers_lists, 'List customer $report_customers{'Zip code distribution'} = [ $fsurl. 'search/report_cust_main-zip.html', 'Zip codes by number of customers' ]; $report_customers{'Customer signup report'} = [ $fsurl. 'graph/report_cust_signup.html', 'New customer signups by date' ], $report_customers{'Advanced customer reports'} = [ $fsurl. 'search/report_cust_main.html', 'by status, signup date, agent, etc.' ] - if $curuser->access_right('List customers') - && $curuser->access_right('List packages'); + if $curuser->access_right('Advanced customer search'); tie my %report_invoices_open, 'Tie::IxHash', 'All open invoices' => [ $fsurl.'search/cust_bill.html?OPEN_date', 'All invoices with an unpaid balance' ], @@ -201,10 +205,10 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { ]; } - if ( $svcdb =~ /^svc_(acct|broadband|hardware)$/ ) { $report_svc{"Advanced $lcsname reports"} = - [ $fsurl."search/report_$svcdb.html", '' ]; - } + [ $fsurl."search/report_$svcdb.html", '' ] + if $svcdb =~ /^svc_(acct|broadband|hardware)$/ + && $curuser->access_right("Services: $name: Advanced search"); if ( $svcdb eq 'svc_phone' ) { @@ -221,7 +225,8 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { } - $report_services{$name} = [ \%report_svc, $longname ]; + $report_services{$name} = [ \%report_svc, $longname ] + if $curuser->access_right("Services: $name"); } @@ -253,14 +258,19 @@ tie my %report_inventory, 'Tie::IxHash', 'Inventory activity' => [ $fsurl.'search/report_h_inventory_item.html', '' ], ; -tie my %report_rating, 'Tie::IxHash', - 'RADIUS sessions' => [ $fsurl.'search/sqlradius.html', '' ], - 'Call Detail Records (CDRs)' => [ $fsurl.'search/report_cdr.html', '' ], - 'Unrateable CDRs' => [ $fsurl.'search/cdr.html?freesidestatus=failed'. - ';cdrbatchnum=_ALL_' ], - 'Time worked' => [ $fsurl.'search/report_rt_transaction.html', '' ], - 'Time worked summary' => [ $fsurl.'search/report_rt_ticket.html', '' ], -; +tie my %report_rating, 'Tie::IxHash'; +$report_rating{'RADIUS sessions'} = [ $fsurl.'search/sqlradius.html', '' ] + if $curuser->access_right("Usage: RADIUS sessions"); +$report_rating{'RADIUS data usage'} = [ $fsurl.'search/report_sqlradius_usage.html', '' ] + if $curuser->access_right("Usage: RADIUS sessions"); +$report_rating{'Call Detail Records (CDRs)'} = [ $fsurl.'search/report_cdr.html', '' ] + if $curuser->access_right("Usage: Call Detail Records (CDRs)"); +$report_rating{'Unrateable CDRs'} = [ $fsurl.'search/cdr.html?freesidestatus=failed;cdrbatchnum=_ALL_' ] + if $curuser->access_right("Usage: Unrateable CDRs"); +if ( $curuser->access_right("Usage: Time worked") ) { + $report_rating{'Time worked'} = [ $fsurl.'search/report_rt_transaction.html', '' ]; + $report_rating{'Time worked summary'} = [ $fsurl.'search/report_rt_ticket.html', '' ]; +} tie my %report_ticketing_statistics, 'Tie::IxHash', 'Tickets per day per Queue' => [ $fsurl.'rt/RTx/Statistics/CallsQueueDay', 'View the number of tickets created, resolved or deleted in a specific Queue, over the requested period of days' ], @@ -272,13 +282,14 @@ tie my %report_ticketing_statistics, 'Tie::IxHash', ; tie my %report_ticketing, 'Tie::IxHash', - 'Resolved by owner' => [ $fsurl.'rt/Tools/Reports/ResolvedByOwner.html', '' ], - 'Resolved in date range' => [ $fsurl.'rt/Tools/Reports/ResolvedByDates.html', '' ], - 'Created in date range' => [ $fsurl.'rt/Tools/Reports/CreatedByDates.html', '' ], - 'separator' => '', +# fix TimeToResolve extension? or redo in a more modern way? +# 'Resolved by owner' => [ $fsurl.'rt/Tools/Reports/ResolvedByOwner.html', '' ], +# 'Resolved in date range' => [ $fsurl.'rt/Tools/Reports/ResolvedByDates.html', '' ], +# 'Created in date range' => [ $fsurl.'rt/Tools/Reports/CreatedByDates.html', '' ], +# 'separator' => '', 'Statistics' => [ \%report_ticketing_statistics, '' ], 'separator2' => '', - 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html', 'List tickets by any criteria' ], + 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html?NewQuery=1', 'List tickets by any criteria' ], ; tie my %report_bill_event, 'Tie::IxHash', @@ -310,6 +321,7 @@ if($curuser->access_right('Financial reports')) { 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ], 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ], 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ], + 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ], 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ], 'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ], 'Unapplied Credits' => [ $fsurl.'search/report_cust_credit.html?unapplied=1', 'Unapplied credit report (by type and/or date range)' ], @@ -337,6 +349,8 @@ if($curuser->access_right('Financial reports')) { tie my %report_menu, 'Tie::IxHash'; $report_menu{'Prospects'} = [ \%report_prospects, 'Prospect reports' ] if $curuser->access_right('List prospects'); +$report_menu{'Quotations'} = [ \%report_quotations, 'Quotation reports' ] + if $curuser->access_right('List quotations'); $report_menu{'Customers'} = [ \%report_customers, 'Customer reports' ] if $curuser->access_right('List customers'); $report_menu{'Invoices'} = [ \%report_invoices, 'Invoice reports' ] @@ -387,10 +401,18 @@ tie my %tools_exporting, 'Tie::IxHash', 'Download database dump' => [ $fsurl. 'misc/dump.cgi', '' ], ; +tie my %tools_ticketing_articles, 'Tie::IxHash', + 'Overview' => [ $fsurl.'rt/Articles/index.html', '' ], + 'Search' => [ $fsurl.'rt/Articles/Article/Search.html', '' ], + 'Topics' => [ $fsurl.'rt/Articles/Topics.html', '' ], +; + tie my %tools_ticketing, 'Tie::IxHash', - 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ], + 'Articles' => [ \%tools_ticketing_articles, '' ], 'My Day' => [ $fsurl.'rt/Tools/MyDay.html', '' ], - 'My Approvals' => [ $fsurl.'rt/Approvals/', '' ], + 'My Reminders' => [ $fsurl.'rt/Tools/MyReminders.html', '' ], + 'Offline' => [ $fsurl.'rt/Tools/Offline.html', '' ], + 'Approval' => [ $fsurl.'rt/Approvals/', '' ], ; $tools_ticketing{'Cron Tool'} = [ $fsurl.'rt/Developer/CronTool/', '' ] if $conf->exists('rt-crontool'); @@ -453,6 +475,7 @@ tie my %config_radius, 'Tie::IxHash', tie my %config_export_svc, 'Tie::IxHash', (); if ( $curuser->access_right('Configuration') ) { $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ]; + $config_export_svc{'Service classes'} = [ $fsurl.'browse/part_svc_class.html', 'Services classes are user-defined, informational types for services' ]; $config_export_svc{'Provisioning exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ]; } $config_export_svc{'Dialup'} = [ \%config_dialup, '' ] @@ -506,6 +529,10 @@ tie my %config_agent, 'Tie::IxHash', 'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ]; ; +tie my %config_sales, 'Tie::IxHash', + 'Sales' => [ $fsurl.'browse/sales.cgi', 'Sales bring in new business.' ], +; + tie my %config_billing_rates, 'Tie::IxHash', 'Rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ], 'Regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ], @@ -541,12 +568,44 @@ if ( $curuser->access_right('Configuration') ) { $config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ]; } +#XXX also to be unified +tie my %config_ticketing_groups, 'Tie::IxHash', + 'Select' => [ $fsurl.'rt/Admin/Groups', '' ], + 'Create' => [ $fsurl.'rt/Admin/Groups/Modify.html?Create=1', '' ], +; + +tie my %config_ticketing_queues, 'Tie::IxHash', + 'Select' => [ $fsurl.'rt/Admin/Queues', '' ], + 'Create' => [ $fsurl.'rt/Admin/Queues/Modify.html?Create=1', '' ], +; + +tie my %config_ticketing_customfields, 'Tie::IxHash', + 'Select' => [ $fsurl.'rt/Admin/CustomFields', '' ], + 'Create' => [ $fsurl.'rt/Admin/CustomFields/Modify.html?Create=1', '' ], +; + +tie my %config_ticketing_articles_classes, 'Tie::IxHash', + 'Select' => [ $fsurl.'rt/Admin/Articles/Classes/', '' ], + 'Create' => [ $fsurl.'rt/Admin/Articles/Classes/Modify.html?Create=1', '' ], +; + +tie my %config_ticketing_articles_customfields, 'Tie::IxHash', + 'Select' => [ $fsurl.'rt/Admin/CustomFields/index.html?type=RT%3A%3AClass-RT%3A%3AArticle', '' ], + 'Create' => [ $fsurl.'rt/Admin/CustomFields/Modify.html?Create=1&LookupType=RT%3A%3AClass-RT%3A%3AArticle', '' ], +; + +tie my %config_ticketing_articles, 'Tie::IxHash', + 'Classes' => [ \%config_ticketing_articles_classes, '' ], + 'Custom Fields' => [ \%config_ticketing_articles_customfields, '' ], +; + tie my %config_ticketing, 'Tie::IxHash', - 'Ticketing Users' => [ $fsurl.'rt/Admin/Users', 'View/Edit ticketing users' ], #XXX to be unified - 'Ticketing Groups' => [ $fsurl.'rt/Admin/Groups', 'View/Edit ticketing groups and group membership' ], #XXX to be unified - 'Ticketing Queues' => [ $fsurl.'rt/Admin/Queues', 'View/Edit ticketing queues and queue-specific properties' ], - 'Ticket Custom Fields' => [ $fsurl.'rt/Admin/CustomFields', 'View/Edit ticketing custom fields' ], - 'Ticketing Global' => [ $fsurl.'rt/Admin/Global', 'View/Edit ticketing configuration applicable to all queues' ], + 'Ticketing Users' => [ $fsurl.'rt/Admin/Users', 'Edit ticketing users' ], #XXX to be unified + 'Ticketing Groups' => [ \%config_ticketing_groups, 'View/Edit ticketing groups and group membership' ], #XXX to be unified + 'Ticketing Queues' => [ \%config_ticketing_queues, 'View/Edit ticketing queues and queue-specific properties' ], + 'Ticket Custom Fields' => [ \%config_ticketing_customfields, 'View/Edit ticketing custom fields' ], + 'Ticketing Global' => [ $fsurl.'rt/Admin/Global', 'View/Edit ticketing configuration applicable to all queues' ], #XXX the individual items + 'Ticketing Articles' => [ \%config_ticketing_articles, '' ], #"System Configuraiton"? useless, just makes people report errors about missing Module::Versions::Report #'Ticketing Tools' => [ $fsurl.'rt/Admin/Tools', '' ], ; @@ -556,9 +615,8 @@ tie my %config_nms, 'Tie::IxHash', tie my %config_misc, 'Tie::IxHash'; $config_misc{'Message templates'} = [ $fsurl.'browse/msg_template.html', 'Templates for customer notices' ] - if $curuser->access_right('Edit templates') - || $curuser->access_right('Edit global templates') - || $curuser->access_right('Configuration'); + if $curuser->access_right(['View templates', 'View global templates', + 'Edit templates', 'Edit global templates', ]); $config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.' ] if $curuser->access_right('Edit advertising sources') || $curuser->access_right('Edit global advertising sources'); @@ -571,6 +629,9 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla || $curuser->access_right('Edit global inventory') || $curuser->access_right('Configuration'); +$config_misc{'FTP targets'} = [ $fsurl.'browse/ftp_target.html', 'FTP servers for billing and payment processing' ] + if $curuser->access_right('Configuration'); + tie my %config_menu, 'Tie::IxHash'; if ( $curuser->access_right('Configuration' ) ) { %config_menu = ( @@ -578,6 +639,7 @@ if ( $curuser->access_right('Configuration' ) ) { 'separator' => '', #its a separator! 'Employees' => [ \%config_employees, '' ], 'Resellers' => [ \%config_agent, '' ], + 'Sales People' => [ \%config_sales, '' ], 'separator2' => '', #its a separator! 'Customers' => [ \%config_cust, '' ], #or this? 'Customers and Contacts' => [ \%config_cust, '' ], @@ -622,18 +684,18 @@ my $doc_link = $conf->config('support-key') eval "use RT;" if $conf->config('ticket_system') eq 'RT_Internal'; -tie my %help_menu, 'Tie::IxHash', 'Billing documentation' => [ $doc_link, 'Freeside documentation' ]; -$help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Request Tracker Wiki' ] - if $conf->config('ticket_system') eq 'RT_Internal'; -$help_menu{'Networking monitoring documentation'} = [ 'http://torrus.org/userguide.pod.html', 'Torrus User Guide' ] - if $conf->config('network_monitoring_system') eq 'Torrus_Internal'; -$help_menu{'separator'} = ''; - +tie my %help_menu, 'Tie::IxHash'; my $agentnum = $conf->config('brand-agent'); if ( $agentnum ) { my $company_name = $conf->config('company_name', $agentnum); $help_menu{"About $company_name"} = [ "javascript:about_freeside()", '' ]; } else { + $help_menu{'Billing documentation'} = [ $doc_link, 'Freeside documentation' ]; + $help_menu{'Ticketing documentation'} = [ 'http://wiki.bestpractical.com/', 'Request Tracker Wiki' ] + if $conf->config('ticket_system') eq 'RT_Internal'; + $help_menu{'Networking monitoring documentation'} = [ 'http://torrus.org/userguide.pod.html', 'Torrus User Guide' ] + if $conf->config('network_monitoring_system') eq 'Torrus_Internal'; + $help_menu{'separator'} = ''; $help_menu{"About Freeside v$FS::VERSION"} = [ "javascript:about_freeside()", '' ]; $help_menu{"About RT v$RT::VERSION"} = [ 'http://www.bestpractical.com/rt', 'Request Tracker Homepage' ] if $conf->config('ticket_system') eq 'RT_Internal'; diff --git a/httemplate/elements/order_pkg_link.html b/httemplate/elements/order_pkg_link.html new file mode 100644 index 000000000..d8aa8fd4e --- /dev/null +++ b/httemplate/elements/order_pkg_link.html @@ -0,0 +1,26 @@ +<& /elements/popup_link-cust_main.html, + 'action' => $p. 'misc/order_pkg.html', + 'label' => $opt{'label'} || emt('Order new package'), + 'actionlabel' => $opt{'actionlabel'} || emt('Order new package'), + 'color' => '#333399', + 'cust_main' => $opt{cust_main}, + 'prospect_main' => $opt{prospect_main}, + 'custnum' => $opt{custnum}, + 'prospectnum' => $opt{prospectnum}, + 'closetext' => emt('Close'), + 'width' => 960, #763, + 'height' => $height, + %optional, +&> +<%init> + +my(%opt) = @_; + +my %optional = + map { $_ => $opt{$_} } + grep $opt{$_}, + qw( lock_pkgpart lock_locationnum qualnum quotationnum svcpart ); + +my $height = $opt{'lock_locationnum'} ? 336 : 576; + +</%init> diff --git a/httemplate/elements/popup_link-cust_main.html b/httemplate/elements/popup_link-cust_main.html index 14137859f..541bb5837 100644 --- a/httemplate/elements/popup_link-cust_main.html +++ b/httemplate/elements/popup_link-cust_main.html @@ -22,7 +22,7 @@ Example: ) </%doc> -% if ( $params->{'cust_main'} ) { +% if ( $custnum || $prospectnum ) { <% include('/elements/popup_link.html', $params ) %>\ % } <%init> @@ -34,13 +34,26 @@ if (ref($_[0]) eq 'HASH') { } else { $params = { %$params, @_ }; } + +my $custnum = $params->{'cust_main'} + ? $params->{'cust_main'}->custnum + : $params->{'custnum'}; + +$params->{'action'} .= ( $params->{'action'} =~ /\?/ ? ';' : '?' ). + "custnum=$custnum" + if $custnum; + +#(maybe i should be called popup_link-cust_or_prospect_main.html now) +my $prospectnum = $params->{'prospect_main'} + ? $params->{'prospect_main'}->prospectnum + : $params->{'prospectnum'}; -$params->{'action'} .= - ( $params->{'action'} =~ /\?/ ? ';' : '?' ). - 'custnum='. $params->{'cust_main'}->custnum; +$params->{'action'} .= ( $params->{'action'} =~ /\?/ ? ';' : '?' ). + "prospectnum=$prospectnum" + if $prospectnum; $params->{'action'} .= ";$_=".$params->{$_} foreach grep $params->{$_}, - qw( lock_pkgpart lock_locationnum qualnum svcpart ); + qw( lock_pkgpart lock_locationnum qualnum quotationnum svcpart ); </%init> diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index 2ec248e32..7a282a34c 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -54,10 +54,7 @@ sub process_whatever { #class method ) %> -% if (!$noinit) { <& /elements/init_overlib.html &> -% $noinit = 1; -% } <SCRIPT TYPE="text/javascript"> @@ -117,9 +114,6 @@ function <%$key%>myCallback( jobnum ) { </SCRIPT> -<%once> -my $noinit = 0; -</%once> <%init> my( $formname, $fields, $action, $url_or_message, $key ) = @_; diff --git a/httemplate/elements/searchbar-ticket.html b/httemplate/elements/searchbar-ticket.html index 774ca9875..30624f7d3 100644 --- a/httemplate/elements/searchbar-ticket.html +++ b/httemplate/elements/searchbar-ticket.html @@ -2,7 +2,7 @@ <FORM ACTION="<% FS::TicketSystem->baseurl %>index.html" METHOD="GET" STYLE="margin:0"> <INPUT NAME="q" TYPE="text" VALUE="<% $ticketing_label |n %>" STYLE="width:<% $width %>" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" CLASS="fstext"><BR> - <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" CLASS="fslink" STYLE="font-size:11px"><% mt('Advanced') |h %></A> + <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html?NewQuery=1" CLASS="fslink" STYLE="font-size:11px"><% mt('Advanced') |h %></A> <INPUT TYPE="submit" VALUE="<% mt('Search tickets') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px"> </FORM> <% $menu_position eq 'left' ? '<BR>' : '' %> diff --git a/httemplate/elements/select-cust-part_pkg.html b/httemplate/elements/select-cust-part_pkg.html index 731beae2a..2d4cd183d 100644 --- a/httemplate/elements/select-cust-part_pkg.html +++ b/httemplate/elements/select-cust-part_pkg.html @@ -27,9 +27,9 @@ Example: my( %opt ) = @_; -my $cust_main = $opt{'cust_main'} - or die "cust_main not specified"; +my $cust_or_prospect_main = $opt{'cust_main'} || $opt{'prospect_main'} + or die "neither cust_main nor prospect_main specified"; -$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ); +$opt{'extra_sql'} .= ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_or_prospect_main->agent ); </%init> diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html index ec37eaf67..2114c07a1 100644 --- a/httemplate/elements/select-cust_pkg-status.html +++ b/httemplate/elements/select-cust_pkg-status.html @@ -3,7 +3,9 @@ <% $onchange %> > +% if ( !$opt{'disable_empty'} ) { <OPTION VALUE="">all +% } % foreach my $option ( @{ $opt{'statuses'} } ) { diff --git a/httemplate/elements/select-part_svc_class.html b/httemplate/elements/select-part_svc_class.html new file mode 100644 index 000000000..280e3e17d --- /dev/null +++ b/httemplate/elements/select-part_svc_class.html @@ -0,0 +1,22 @@ +<% include( '/elements/select-table.html', + 'table' => 'part_svc_class', + 'name_col' => 'classname', + 'value' => $classnum, + 'empty_label' => '(none)', + 'hashref' => \%hash, + %opt, + ) +%> +<%init> + +my %opt = @_; +my $classnum = $opt{'curr_value'} || $opt{'value'}; + +my %hash = (); +$hash{'disabled'} = '' unless $opt{'showdisabled'}; + + +$opt{'records'} = delete $opt{'part_svc_class'} + if $opt{'part_svc_class'}; + +</%init> diff --git a/httemplate/elements/select-rt-customfield.html b/httemplate/elements/select-rt-customfield.html index 7a45bb14b..85758d585 100644 --- a/httemplate/elements/select-rt-customfield.html +++ b/httemplate/elements/select-rt-customfield.html @@ -3,9 +3,6 @@ <OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION> % } </SELECT> -<%once> -RT::Init(); -</%once> <%init> my %opt = @_; my $lookuptype = $opt{lookuptype}; diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html index c0dde7414..c0cd7a50b 100644 --- a/httemplate/elements/select-table.html +++ b/httemplate/elements/select-table.html @@ -93,10 +93,17 @@ Example: % ) % { % my $recvalue = $record->$key(); +% my $selected; +% if ( $opt{'all_selected'} ) { +% $selected = 1; +% } elsif ( $opt{'compare_sub'} && !ref($value) ) { +% $selected = &{ $opt{'compare_sub'} }( $value, $recvalue ); +% } else { +% $selected = ( ref($value) && $value->{$recvalue} ) +% || ( $value && $value eq $recvalue ); #not == because of value_col +% } <OPTION VALUE="<% $recvalue %>" - <% $opt{'all_selected'} || ref($value) && $value->{$recvalue} || $value && $value eq $recvalue # not == because of value_col - ? ' SELECTED' : '' - %> + <% $selected ? ' SELECTED' : '' %> % foreach my $att ( @{ $opt{'extra_option_attributes'} } ) { data-<% $att %>="<% $record->$att() |h %>" % } @@ -174,24 +181,29 @@ if ( $opt{'records'} ) { }); } -unless ( $value < 1 # !$value #ignore negatives too - or ref($value) +if ( ref( $value ) eq 'ARRAY' ) { + $value = { map { $_ => 1 } @$value }; +} + +unless ( !ref($value) && $value < 1 # !$value #ignore negatives too or ! exists( $opt{hashref}->{disabled} ) #?? - or grep { $value == $_->$key() } @records + #or grep { $value == $_->$key() } @records ) { delete $opt{hashref}->{disabled}; - $opt{hashref}->{$key} = $value; - my $record = qsearchs( { - 'table' => $opt{table}, - 'addl_from' => $opt{'addl_from'}, - 'hashref' => $hashref, - 'extra_sql' => $extra_sql, - }); - push @records, $record if $record; -} -if ( ref( $value ) eq 'ARRAY' ) { - $value = { map { $_ => 1 } @$value }; + foreach my $v ( ref($value) ? keys %$value : ($value) ) { + next if grep { $v == $_->$key() } @records; + + $opt{hashref}->{$key} = $v; + my $record = qsearchs( { + 'table' => $opt{table}, + 'addl_from' => $opt{'addl_from'}, + 'hashref' => $hashref, + 'extra_sql' => $extra_sql, + }); + push @records, $record if $record; + + } } my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : (); diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js index ee4c5e7f3..f6564a55e 100644 --- a/httemplate/elements/standardize_locations.js +++ b/httemplate/elements/standardize_locations.js @@ -7,7 +7,7 @@ function form_address_info() { 'onlyship': 1, % } else { % if ( $withfirm ) { - 'company': cf.elements['<% $main_prefix %>company'].value, + 'company', cf.elements['company'].value, % } 'address1': cf.elements['<% $main_prefix %>address1'].value, 'address2': cf.elements['<% $main_prefix %>address2'].value, @@ -16,9 +16,6 @@ function form_address_info() { 'zip': cf.elements['<% $main_prefix %>zip'].value, 'country': cf.elements['<% $main_prefix %>country'].value, % } -% if ( $withfirm ) { - 'ship_company': cf.elements['<% $ship_prefix %>company'].value, -% } % if ( $withcensus ) { 'ship_censustract': cf.elements['enter_censustract'].value, % } diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html new file mode 100644 index 000000000..6d1a45a0d --- /dev/null +++ b/httemplate/elements/table-tickets.html @@ -0,0 +1,159 @@ +<& /elements/form-create_ticket.html, object => $object &> + | +View +<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> | +<A HREF="<% $res_link %>"><% mt('resolved') |h %></A> + <BR> + +<& /elements/table-grid.html &> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + +<TR> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Queue') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Owner') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Due') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Estimated Time') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Priority') |h %></TH> +% if ( $ss_priority ) { + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Customer Priority') |h %></TH> +% } +% if ( $object->isa('FS::cust_main') ) { + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Service') |h %></TH> +% } +</TR> + +% foreach my $ticket ( @tickets ) { +% my $href = FS::TicketSystem->href_ticket($ticket->{id}); +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF=<%$href%>><% $ticket->{id} %></A> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF=<%$href%>><% $ticket->{subject} %></A> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{status} %> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{queue} %> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{owner} %> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $date_formatter->($ticket->{due}) %> + </TD> + + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{timeestimated} %> + </TD> + + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{content} + ? $ticket->{content}.' ('.$ticket->{priority}.')' + : $ticket->{priority} + %> + </TD> + +% if ( $ss_priority ) { + <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <% $ticket->{"CF.{$ss_priority}"} %> + </TD> +% } +% if ( $object->isa('FS::cust_main') ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE=-1><B> +% foreach (@{ $ticket->{svcnums} }) { +% my $cust_svc = FS::cust_svc->by_key($_) or next; + <% FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) %> + <BR> +% } + </B></FONT></TD> +% } + + </TR> + +% } + +</TABLE> + +<%init> +use Date::Parse qw(str2time); +use Date::Format qw(time2str); + +my %opt = @_; +my $conf = new FS::Conf; + +my $object = $opt{'object'}; +$object = $object->cust_svc if $object->isa('FS::svc_Common'); +my( @tickets ) = $object->tickets; + +my ($openlabel, $open_link, $res_link, $thing); +$openlabel = join('/', FS::TicketSystem->statuses ); + +# not the nicest way to do this--FS::has_tickets_Common? +if ( $object->isa('FS::cust_main') ) { + $thing = 'customer'; + $open_link = FS::TicketSystem->href_customer_tickets($object->custnum); + + $res_link = FS::TicketSystem->href_customer_tickets( + $object->custnum, + { 'statuses' => [ 'resolved' ] } + ); +} +elsif ( $object->isa('FS::cust_svc') ) { + $thing = 'service'; + $open_link = FS::TicketSystem->href_service_tickets($object->svcnum); + + $res_link = FS::TicketSystem->href_service_tickets( + $object->svcnum, + { 'statuses' => [ 'resolved' ] } + ); +} + +my $ss_priority = FS::TicketSystem->selfservice_priority; +if ( $ss_priority ) { + my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1; + use sort 'stable'; + # sort in the following way: + @tickets = sort { + # within a severity level... + ( $a->{'content'} eq $b->{'content'} ) ? ( + # no-priority tickets sort last + ( + ($a->{'_selfservice_priority'} eq '') <=> + ($b->{'_selfservice_priority'} eq '') + ) || + # otherwise obey ticket_system-priority_reverse + ( $dir * + ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'}) + ) + ) : 0; # but don't rearrange between severity levels + } @tickets; +} + +my $format = $conf->config('date_format') || '%Y-%m-%d'; + +my $date_formatter = sub { + my $time = str2time($_[0], 'GMT'); + # exclude times within 24 hours of zero + ($time > 86400) ? time2str($format, $time) : ''; +}; + +</%init> diff --git a/httemplate/elements/tr-amount_fee.html b/httemplate/elements/tr-amount_fee.html new file mode 100644 index 000000000..12488521a --- /dev/null +++ b/httemplate/elements/tr-amount_fee.html @@ -0,0 +1,100 @@ + <TR> + <TH ALIGN="right"><% mt('Payment amount') |h %></TH> + <TD COLSPAN=7> + <TABLE><TR><TD BGCOLOR="#ffffff"> + <% $money_char %><INPUT NAME = "amount" + ID = "amount" + TYPE = "text" + VALUE = "<% $amount %>" + SIZE = 8 + STYLE = "text-align:right;" +% if ( $fee ) { + onChange = "amount_changed(this)" + onKeyDown = "amount_changed(this)" + onKeyUp = "amount_changed(this)" + onKeyPress = "amount_changed(this)" +% } + > + </TD><TD BGCOLOR="#cccccc"> +% if ( $fee ) { + <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>"> + <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>"> + <B><FONT SIZE='+1'><% $fee_op %></FONT> + <% $money_char . $fee %> + </B> + <% $fee_pkg->pkg |h %> + <B><FONT SIZE='+1'>=</FONT></B> + </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue"> + <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT> + +% } + </TD></TR></TABLE> + </TD> + </TR> + +% if ( $fee ) { + + <SCRIPT TYPE="text/javascript"> + + function amount_changed(what) { + + + var total = ''; + if ( what.value.length ) { + total = parseFloat(what.value) <% $fee_op %> <% $fee %>; + /* total = Math.round(total*100)/100; */ + total = '<% $money_char %>' + total.toFixed(2); + } + + var total_cell = document.getElementById('ajax_total_cell'); + total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>'; + + } + + </SCRIPT> + +% } + +<%init> + +my %opt = @_; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $fee = ''; +my $fee_pkg = ''; +my $fee_display = ''; +my $fee_op = ''; + +if ( $opt{'process-pkgpart'} + and ! $opt{'process-skip_first'} || $opt{'num_payments'} + ) +{ + + $fee_display = $opt{'process-display'} || 'add'; + $fee_op = $fee_display eq 'add' ? '+' : '-'; + + $fee_pkg = + qsearchs('part_pkg', { pkgpart=>$opt{'process-pkgpart'} } ); + + #well ->unit_setup or ->calc_setup both call for a $cust_pkg + # (though ->unit_setup doesn't use it...) + $fee = $fee_pkg->option('setup_fee') + if $fee_pkg; #in case.. better than dying with a perl traceback + +} + +my $amount = $opt{'amount'}; +if ( $amount > 0 ) { + $amount += $fee + if $fee && $fee_display eq 'subtract'; + + #&{ $opt{post_fee_callback} }( \$amount ) if $opt{post_fee_callback}; + $amount += $amount * $opt{'surcharge_percentage'}/100 + if $opt{'surcharge_percentage'} > 0; + + $amount = sprintf("%.2f", $amount); +} + +</%init> diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html index ca5de86b4..1ca22f6d4 100644 --- a/httemplate/elements/tr-cust_svc.html +++ b/httemplate/elements/tr-cust_svc.html @@ -24,7 +24,7 @@ Usage: <TD ALIGN="right" VALIGN="top"><% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %></TD> - <TD STYLE="padding-bottom:0px"><B><% + <TD STYLE="padding-bottom:0px"><B><% $cust_svc->agent_svcid ? $cust_svc->agent_svcid.': ' : '' %><% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %></B></TD> <TD ALIGN="right"><% FS::UI::Web::svc_export_links($m, $part_svc, $cust_svc) %> diff --git a/httemplate/elements/tr-fixed-cust_main.html b/httemplate/elements/tr-fixed-cust_main.html new file mode 100644 index 000000000..00bcb66d8 --- /dev/null +++ b/httemplate/elements/tr-fixed-cust_main.html @@ -0,0 +1,15 @@ +% if ( $cust_main ) { + <% include('tr-fixed.html', %opt ) %> +% } +<%init> + +my %opt = @_; + +my $value = $opt{'curr_value'} || $opt{'value'}; + +my $cust_main = $value ? qsearchs('cust_main', {custnum=>$value} ) + : ''; + +$opt{'formatted_value'} = $cust_main->name if $cust_main; + +</%init> diff --git a/httemplate/elements/tr-fixed-date.html b/httemplate/elements/tr-fixed-date.html new file mode 100644 index 000000000..716e5ceb8 --- /dev/null +++ b/httemplate/elements/tr-fixed-date.html @@ -0,0 +1,13 @@ +<% include('tr-fixed.html', %opt ) %> +<%init> + +my %opt = @_; + +my $value = $opt{'curr_value'} || $opt{'value'}; + +my $conf = new FS::Conf; +my $date_format = $conf->config('date_format') || '%m/%d/%Y'; + +$opt{'formatted_value'} = time2str($date_format, $value); + +</%init> diff --git a/httemplate/elements/tr-fixed-prospect_main.html b/httemplate/elements/tr-fixed-prospect_main.html new file mode 100644 index 000000000..8da0ffb84 --- /dev/null +++ b/httemplate/elements/tr-fixed-prospect_main.html @@ -0,0 +1,15 @@ +% if ( $prospect_main ) { + <% include('tr-fixed.html', %opt ) %> +% } +<%init> + +my %opt = @_; + +my $value = $opt{'curr_value'} || $opt{'value'}; + +my $prospect_main = $value ? qsearchs('prospect_main', {prospectnum=>$value} ) + : ''; + +$opt{'formatted_value'} = $prospect_main->name if $prospect_main; + +</%init> diff --git a/httemplate/elements/tr-fixed.html b/httemplate/elements/tr-fixed.html index f358343dd..dd07d90b6 100644 --- a/httemplate/elements/tr-fixed.html +++ b/httemplate/elements/tr-fixed.html @@ -13,13 +13,15 @@ my %opt = @_; my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; my $value = $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'}; -#compatibility with select-table and friends -if ( $opt{'multiple'} ) { - $value = [ split(/\s*,\s*/, $value) ] if !ref $value; - $value = join('<BR>', map {encode_entities($_)} @$value); -} -else { - $value = encode_entities($value) + +unless ( $opt{'noescape'} ) { + #compatibility with select-table and friends + if ( $opt{'multiple'} ) { + $value = [ split(/\s*,\s*/, $value) ] if !ref $value; + $value = join('<BR>', map {encode_entities($_)} @$value); + } else { + $value = encode_entities($value) + } } </%init> diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html index ce03c40f5..321bd6b32 100644 --- a/httemplate/elements/tr-select-agent.html +++ b/httemplate/elements/tr-select-agent.html @@ -22,7 +22,7 @@ Example: ); </%doc> -% if ( scalar(@agents) == 1 ) { +% if ( scalar(@agents) == 1 || $opt{'fixed'} ) { <INPUT TYPE = "hidden" NAME = "<% $opt{'field'} || 'agentnum' %>" @@ -30,9 +30,20 @@ Example: VALUE = "<% $agents[0]->agentnum %>" > -%# YUCK. empty row so we don't throw g_row in edit.html off :/ - <TR> - </TR> +% if ( scalar(@agents) != 1 ) { + <TR> + <TD ALIGN="right"><% $opt{'label'} || emt('Agent') %></TD> + <TD BGCOLOR="#dddddd" <% $colspan %>> +% my $agent = qsearchs('agent', { 'agentnum' => $agentnum }); + <% $agent ? $agent->agent : '(all)' |h %> + </TD> + </TR> + +% } else { # YUCK. empty row so we don't throw g_row in edit.html off :/ + <TR> + </TR> +% } +% % } else { <TR> diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html index 767d23264..848ab0a4b 100644 --- a/httemplate/elements/tr-select-cust-part_pkg.html +++ b/httemplate/elements/tr-select-cust-part_pkg.html @@ -51,8 +51,11 @@ } - get_part_pkg( <% $cust_main->custnum %>, classnum, update_part_pkg ); - + get_part_pkg( <% $cust_main ? $cust_main->custnum : '0' %>, + <% $prospect_main ? $prospect_main->prospectnum : '0' %>, + classnum, + update_part_pkg + ); } </SCRIPT> @@ -74,10 +77,11 @@ <TH ALIGN="right"><% mt('Package') |h %></TH> <TD COLSPAN=7> <& /elements/select-cust-part_pkg.html, - 'curr_value' => $opt{'curr_value'}, #$pkgpart - 'classnum' => $opt{'classnum'}, - 'cust_main' => $opt{'cust_main'}, #$cust_main - 'onchange' => 'pkg_changed', + 'curr_value' => $opt{'curr_value'}, #$pkgpart + 'classnum' => $opt{'classnum'}, + 'cust_main' => $opt{'cust_main'}, #$cust_main + 'prospect_main' => $opt{'prospect_main'}, #$prospect_main + 'onchange' => 'pkg_changed', &> </TD> </TR> @@ -91,8 +95,13 @@ my %opt = @_; my $pre_label = $opt{'pre_label'} || ''; $pre_label .= ' ' if length($pre_label) && $pre_label =~ /\S$/; -my $cust_main = $opt{'cust_main'} - or die "cust_main not specified"; +my $cust_main = $opt{'cust_main'}; +my $prospect_main = $opt{'prospect_main'}; + +die "neither cust_main nor prospect_main specified" + unless $cust_main || $prospect_main; + +my $agent = $cust_main ? $cust_main->agent : $prospect_main->agent; #"normal" part_pkg agent virtualization (agentnum or type) my @part_pkg = qsearch({ @@ -101,7 +110,7 @@ my @part_pkg = qsearch({ 'hashref' => { 'disabled' => '' }, 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ). - ' AND '. FS::part_pkg->agent_pkgs_sql( $opt{'cust_main'}->agent ), + ' AND '. FS::part_pkg->agent_pkgs_sql( $agent ), }); my @pkg_class = diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html index 801152e9f..b472cc5a7 100644 --- a/httemplate/elements/tr-select-cust_location.html +++ b/httemplate/elements/tr-select-cust_location.html @@ -11,7 +11,6 @@ Example: #optional 'empty_label' => '(default service address)', - 'disable_empty' => 0, #1 to disable ) </%doc> @@ -52,12 +51,13 @@ Example: var ftype = what.form.<%$_%>.tagName; if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff'; % } - - if ( what.form.location_type && - what.form.location_type.options[what.form.location_type.selectedIndex].value ) { - what.form.location_number.disabled = false; - what.form.location_number.style.backgroundColor = '#ffffff'; - } +% if ( $opt{'alt_format'} ) { + if ( what.form.location_type && + what.form.location_type.options[what.form.location_type.selectedIndex].value ) { + what.form.location_number.disabled = false; + what.form.location_number.style.backgroundColor = '#ffffff'; + } +% } } function locationnum_changed(what) { @@ -102,25 +102,8 @@ Example: return; } - if ( locationnum == 0 ) { //(default service address) -% if ( $cust_main ) { - what.form.address1.value = <% $cust_main->get($prefix.'address1') |js_string %>; - what.form.address2.value = <% $cust_main->get($prefix.'address2') |js_string %>; - what.form.city.value = <% $cust_main->get($prefix.'city') |js_string %>; - what.form.zip.value = <% $cust_main->get($prefix.'zip') |js_string %>; - - changeSelect(what.form.country, <% $cust_main->get($prefix.'country') | js_string %> ); - - country_changed( what.form.country, - fix_state_factory( <% $cust_main->get($prefix.'state') | js_string %>, - <% $cust_main->get($prefix.'county') | js_string %> - ) - ); -% } - - } else { - get_location( locationnum, update_location ); - } +%# default service address is now just another location + get_location( locationnum, update_location ); % if ( $editable ) { if ( locationnum == 0 ) { @@ -204,14 +187,16 @@ Example: ID = "locationnum" onChange = "locationnum_changed(this);" > -% if ( !$prospect_main && !$opt{'disable_empty'} ) { - <OPTION VALUE=""><% $opt{'empty_label'} || '(default service address)' |h %> +% if ( $cust_main ) { + <OPTION VALUE="<% $cust_main->ship_locationnum %>"><% $opt{'empty_label'} || '(default service address)' |h %> % } % if ( $opt{'is_optional'} ) { <OPTION VALUE="-2" <% $locationnum == -2 ? 'SELECTED' : ''%>><% $opt{'optional_label'} || '(not required)' |h %> % } % % foreach my $loc ( @cust_location ) { +% # don't show the ship_location redundantly +% next if $cust_main && $cust_main->ship_locationnum == $loc->locationnum; <OPTION VALUE="<% $loc->locationnum %>" <% $locationnum == $loc->locationnum ? 'SELECTED' : '' %> ><% $loc->line |h %> @@ -232,9 +217,12 @@ Example: 'no_asterisks' => 1, 'no_bold' => $opt{'no_bold'}, 'alt_format' => $opt{'alt_format'}, + 'enable_coords'=> 1, ) %> - +<SCRIPT TYPE="text/javascript"> + locationnum_changed(document.getElementById('locationnum')); +</SCRIPT> <%init> my $conf = new FS::Conf; @@ -247,8 +235,7 @@ my $cgi = $opt{'cgi'}; my $cust_pkg = $opt{'cust_pkg'}; my $cust_main = $opt{'cust_main'}; my $prospect_main = $opt{'prospect_main'}; - -my $prefix = ($cust_main && length($cust_main->ship_last)) ? 'ship_' : ''; +die "cust_main or prospect_main required" unless $cust_main or $prospect_main; my $locationnum = ''; if ( $cgi->param('error') ) { @@ -260,9 +247,9 @@ if ( $cgi->param('error') ) { } elsif ($prospect_main) { my @cust_location = $prospect_main->cust_location; $locationnum = $cust_location[0]->locationnum if scalar(@cust_location)==1; - } else { #? + } else { #$cust_main $cgi->param('locationnum') =~ /^(\-?\d*)$/ or die "illegal locationnum"; - $locationnum = $1; + $locationnum = $1 || $cust_main->ship_locationnum; } } @@ -278,7 +265,7 @@ if ( $opt{'alt_format'} ) { push @location_fields, qw( location_type location_number location_kind ); } -my $cust_location; +my $cust_location; #the one that shows by default in the location edit space if ( $locationnum && $locationnum > 0 ) { $cust_location = qsearchs('cust_location', { 'locationnum' => $locationnum } ) or die "unknown locationnum"; @@ -291,7 +278,7 @@ if ( $locationnum && $locationnum > 0 ) { $cust_location->$_( $pkg_location->$_ ) foreach @location_fields; $opt{'empty_label'} ||= 'package address: '.$pkg_location->line; } elsif ( $cust_main ) { - $cust_location->$_( $cust_main->get($prefix.$_) ) foreach @location_fields; + $cust_location = $cust_main->ship_location; #I think } } @@ -304,7 +291,7 @@ my $location_sort = sub { or lc($a->address2) cmp lc($b->address2) }; -my @cust_location = (); +my @cust_location; push @cust_location, $cust_main->cust_location if $cust_main; push @cust_location, $prospect_main->cust_location if $prospect_main; push @cust_location, $cust_location @@ -314,14 +301,14 @@ push @cust_location, $cust_location @cust_location = sort $location_sort grep !$_->disabled, @cust_location; $cust_location = $cust_location[0] - if ( $prospect_main || $opt{'disable_empty'} ) + if ( $prospect_main ) && !$opt{'is_optional'} && @cust_location; my $disabled = ( $locationnum < 0 || ( $editable && $locationnum ) - || ( ( $prospect_main || $opt{'disable_empty'} ) + || ( $prospect_main && !$opt{'is_optional'} && !@cust_location && $addnew ) ) diff --git a/httemplate/elements/tr-select-discount.html b/httemplate/elements/tr-select-discount.html index 30a60ec85..ee862519f 100644 --- a/httemplate/elements/tr-select-discount.html +++ b/httemplate/elements/tr-select-discount.html @@ -6,7 +6,7 @@ % } else { <TR> - <TD ALIGN="right" WIDTH="176"><% $opt{'label'} || '<B>'.emt('Discount').'</B>' %></TD> + <TD ALIGN="right" WIDTH="275"><% $opt{'label'} || '<B>'.emt('Discount').'</B>' %></TD> <TD <% $colspan %>> <% include( '/elements/select-discount.html', 'curr_value' => $discountnum, @@ -74,6 +74,16 @@ ) %> +%# <% include( '/elements/tr-checkbox.html', +%# 'label' => '<B>Apply discount to add-on packages</B>', +%# 'field' => $name.'_linked', +%# 'id' => $name.'_linked', +%# 'curr_value' => scalar($cgi->param($name.'_linked')), +%# 'value' => 'Y', +%# 'colspan' => $opt{'colspan'}, +%# ) +%# %> + <SCRIPT TYPE="text/javascript"> % my $ge = 'document.getElementById'; @@ -136,6 +146,10 @@ <% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden'; <% $ge %>('<% $name %>_percent_input0').style.display = 'none'; <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden'; +// <% $ge %>('<% $name %>_linked_label0').style.display = 'none'; +// <% $ge %>('<% $name %>_linked_label0').style.visibility = 'hidden'; +// <% $ge %>('<% $name %>_linked').style.display = 'none'; +// <% $ge %>('<% $name %>_linked').style.visibility = 'hidden'; } else if ( <% $name %>__type == 'Amount' ) { <% $ge %>('<% $name %>_amount_label0').style.display = ''; <% $ge %>('<% $name %>_amount_label0').style.visibility = ''; @@ -145,6 +159,11 @@ <% $ge %>('<% $name %>_percent_label0').style.visibility = 'hidden'; <% $ge %>('<% $name %>_percent_input0').style.display = 'none'; <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden'; + <% $ge %>('<% $name %>_percent_input0').style.visibility = 'hidden'; +// <% $ge %>('<% $name %>_linked_label0').style.display = 'none'; +// <% $ge %>('<% $name %>_linked_label0').style.visibility = 'hidden'; +// <% $ge %>('<% $name %>_linked').style.display = 'none'; +// <% $ge %>('<% $name %>_linked').style.visibility = 'hidden'; } else if ( <% $name %>__type == 'Percentage' ) { <% $ge %>('<% $name %>_amount_label0').style.display = 'none'; <% $ge %>('<% $name %>_amount_label0').style.visibility = 'hidden'; @@ -154,6 +173,11 @@ <% $ge %>('<% $name %>_percent_label0').style.visibility = ''; <% $ge %>('<% $name %>_percent_input0').style.display = ''; <% $ge %>('<% $name %>_percent_input0').style.visibility = ''; + <% $ge %>('<% $name %>_percent_input0').style.visibility = ''; +// <% $ge %>('<% $name %>_linked_label0').style.display = ''; +// <% $ge %>('<% $name %>_linked_label0').style.visibility = ''; +// <% $ge %>('<% $name %>_linked').style.display = ''; +// <% $ge %>('<% $name %>_linked').style.visibility = ''; } } diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html index 765aa8400..5041f7f73 100644 --- a/httemplate/elements/tr-select-part_referral.html +++ b/httemplate/elements/tr-select-part_referral.html @@ -14,13 +14,7 @@ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'refnum' %>" VALUE="<% $opt{'part_referrals'}->[0]->refnum %>"> % } else { - - <TR> -% if ( $opt{'label'} ) { - <TD ALIGN="right"><% $opt{'label'} %></TD> -% } else { - <TH ALIGN="right"><%$r%><% mt('Advertising source') |h %></TH> -% } + <& /elements/tr-td-label.html, label => 'Advertising source', %opt &> <TD COLSPAN="<% $colspan %>"> <& /elements/select-part_referral.html, 'curr_value' => $refnum, diff --git a/httemplate/elements/tr-select-part_svc_class.html b/httemplate/elements/tr-select-part_svc_class.html new file mode 100644 index 000000000..2f4b09381 --- /dev/null +++ b/httemplate/elements/tr-select-part_svc_class.html @@ -0,0 +1,27 @@ +% if ( scalar(@{ $opt{'part_svc_class'} }) == 0 ) { + + <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE=""> + +% } else { + + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Service class' %></TD> + <TD> + <% include( '/elements/select-part_svc_class.html', + 'curr_value' => $classnum, + %opt + ) + %> + </TD> + </TR> + +% } + +<%init> + +my %opt = @_; +my $classnum = $opt{'curr_value'} || $opt{'value'}; + +$opt{'part_svc_class'} ||= [ qsearch( 'part_svc_class', { disabled=>'' } ) ]; + +</%init> diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html index 5a79d68ef..c1df10b94 100755 --- a/httemplate/elements/tr-select-reason.html +++ b/httemplate/elements/tr-select-reason.html @@ -32,8 +32,15 @@ Example: <SCRIPT TYPE="text/javascript"> function sh_add<% $func_suffix %>() { + var hints = <% encode_json(\@hints) %>; + var select_reason = document.getElementById('<% $id %>'); - if (document.getElementById('<% $id %>').selectedIndex == 0){ +% if ( $class eq 'S' ) { + document.getElementById('<% $id %>_hint').innerHTML = + hints[select_reason.selectedIndex]; +% } + + if (select_reason.selectedIndex == 0){ <% $controlledbutton ? $controlledbutton.'.disabled = true;' : ';' %> }else{ <% $controlledbutton ? $controlledbutton.'.disabled = false;' : ';' %> @@ -41,8 +48,8 @@ Example: %if ($curuser->access_right($add_access_right)){ - if (document.getElementById('<% $id %>').selectedIndex == - (document.getElementById('<% $id %>').length - 1)) { + if (select_reason.selectedIndex == + (select_reason.length - 1)) { document.getElementById('new<% $id %>').disabled = false; document.getElementById('new<% $id %>').style.display = 'inline'; document.getElementById('new<% $id %>Label').style.display = 'inline'; @@ -113,6 +120,13 @@ Example: </TR> % } +% if ( $class eq 'S' ) { +<TR> + <TD COLSPAN=2 ALIGN="center" id="<% $id %>_hint"> + </TD> +</TR> +% } + <TR> <TD ALIGN="right"> <P id="new<% $id %>Label" style="display:<% $display %>"><% mt('New Reason') |h %></P> @@ -184,6 +198,43 @@ my @reasons = qsearch({ order_by => 'ORDER BY reason_type.type ASC, reason.reason ASC', }); +my @hints; +if ( $class eq 'S' ) { + my $conf = FS::Conf->new; + @hints = ( '' ); + foreach my $reason (@reasons) { + if ( $reason->unsuspend_pkgpart ) { + my $part_pkg = FS::part_pkg->by_key($reason->unsuspend_pkgpart); + if ( $part_pkg ) { + if ( $part_pkg->option('setup_fee',1) > 0 and + $part_pkg->option('recur_fee',1) == 0 ) { + # the usual case + push @hints, + mt('A [_1] unsuspension fee will apply.', + ($conf->config('money_char') || '$') . + sprintf('%.2f', $part_pkg->option('setup_fee')) + ); + } else { + # oddball cases--not really supported + push @hints, + mt('An unsuspension package will apply: [_1]', + $part_pkg->price_info + ); + } + } else { #no $part_pkg + push @hints, + '<FONT COLOR="#ff0000">Unsuspend pkg #'.$reason->unsuspend_pkgpart. + ' not found.</FONT>'; + } + } else { #no unsuspend_pkgpart + push @hints, ''; + } + } + push @hints, ''; # for the "new reason" case + @hints = map {'<FONT SIZE="-1">'.$_.'</FONT>'} @hints; +} + + my $curuser = $FS::CurrentUser::CurrentUser; </%init> diff --git a/httemplate/elements/tr-select-voip_class.html b/httemplate/elements/tr-select-voip_class.html new file mode 100644 index 000000000..dcc1487cc --- /dev/null +++ b/httemplate/elements/tr-select-voip_class.html @@ -0,0 +1,24 @@ +<& tr-td-label.html, label => 'Category', @_ &> +<TD> +<SELECT NAME="<% $opt{'field'} %>"> +% while(@options) { +% my $value = shift @options; +% my $selected = ($value eq $opt{'curr_value'}) ? 'SELECTED' : ''; + <OPTION VALUE="<% $value %>" <% $selected %>><% shift @options %></OPTION> +% } +</SELECT> +</TD></TR> +<%init> +my %opt = ( + field => 'fcc_voip_class', + label => 'Category', + @_ +); +my @options = ( + '' => '', + 1 => 'VoIP without Broadband', + 2 => 'VoIP with Broadband', + 3 => 'Wholesale VoIP' +); + +</%init> diff --git a/httemplate/elements/tr-svc_export_machine.html b/httemplate/elements/tr-svc_export_machine.html new file mode 100644 index 000000000..92b6ac1d7 --- /dev/null +++ b/httemplate/elements/tr-svc_export_machine.html @@ -0,0 +1,37 @@ +% foreach my $part_export (@part_export) { +% my $label = ( $part_export->exportname +% ? $part_export->exportname +% : $part_export->label +% ). +% ' hostname'; +% +% my $element = 'exportnum'. $part_export->exportnum. 'machinenum'; +% my $machinenum = $opt{cgi}->param($element); +% if ( ! $machinenum && $opt{svc}->svcnum ) { +% my $svc_export_machine = qsearchs('svc_export_machine', { +% 'svcnum' => $opt{svc}->svcnum, +% 'exportnum' => $part_export->exportnum, +% }); +% $machinenum = $svc_export_machine->machinenum if $svc_export_machine; +% } + + <& /elements/tr-select-table.html, + 'label' => $label, + 'element_name' => 'exportnum'. $part_export->exportnum. 'machinenum', + 'table' => 'part_export_machine', + 'name_col' => 'machine', + 'hashref' => { 'exportnum' => $part_export->exportnum, + 'disabled' => '', + }, + 'curr_value' => $machinenum, + 'empty_label' => 'Select export hostname', + &> +% } +<%init> + +my %opt = @_; + +my @part_export = grep { $_->machine eq '_SVC_MACHINE' } + $opt{part_svc}->part_export; + +</%init> diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css index 73699b6f9..e6a39f675 100644 --- a/httemplate/elements/xmenu.css +++ b/httemplate/elements/xmenu.css @@ -20,7 +20,7 @@ .webfx-menu { position: absolute; - z-index: 100; + z-index: 9999; visibility: hidden; border: 1px solid #7e0079; -moz-border-radius: 4px; diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css index ff0d6f684..7a42254e4 100644 --- a/httemplate/elements/xmenu.top.css +++ b/httemplate/elements/xmenu.top.css @@ -20,7 +20,7 @@ .webfx-menu { position: absolute; - z-index: 100; + z-index: 9999; visibility: hidden; border: 1px solid #7e0079; -moz-border-radius: 4px; diff --git a/httemplate/graph/cust_bill_pkg.cgi b/httemplate/graph/cust_bill_pkg.cgi index af4d045fe..c334ae9e7 100644 --- a/httemplate/graph/cust_bill_pkg.cgi +++ b/httemplate/graph/cust_bill_pkg.cgi @@ -8,6 +8,7 @@ 'graph_labels' => \@labels, 'colors' => \@colors, 'links' => \@links, + 'no_graph' => \@no_graph, 'remove_empty' => 1, 'bottom_total' => 1, 'bottom_link' => $bottom_link, @@ -49,6 +50,21 @@ elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { die "agentnum $agentnum not found!" unless $sel_agent; } my $title = $sel_agent ? $sel_agent->agent.' ' : ''; + +my( $refnum, $sel_part_referral, $all_part_referral ) = ('', '', ''); +if ( $cgi->param('refnum') eq 'all' ) { + $refnum = 0; + $all_part_referral = 'ALL'; +} +elsif ( $cgi->param('refnum') =~ /^(\d+)$/ ) { + $refnum = $1; + $bottom_link .= "refnum=$refnum;"; + $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } ); + die "part_referral $refnum not found!" unless $sel_part_referral; +} +$title .= $sel_part_referral->referral.' ' + if $sel_part_referral; + $title .= 'Sales Report (Gross)'; $title .= ', average per customer package' if $average_per_cust_pkg; @@ -103,6 +119,7 @@ my @params = (); my @labels = (); my @colors = (); my @links = (); +my @no_graph; my @components = ( 'SRU' ); # split/omit components as appropriate @@ -119,6 +136,11 @@ elsif ( $use_usage == 2 ) { $components[-1] =~ s/U//; } +# Categorization of line items goes +# Agent -> Referral -> Package class -> Component (setup/recur/usage) +# If per-agent totals are enabled, they go under the Agent level. +# There aren't any other kinds of subtotals. + foreach my $agent ( $all_agent || $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) { my $col_scheme = Color::Scheme->new @@ -130,43 +152,91 @@ foreach my $agent ( $all_agent || $sel_agent || qsearch('agent', { 'disabled' => ### fixup the color handling for package classes... ### and usage - my $n = 0; - - foreach my $pkg_class ( @pkg_class ) { - foreach my $component ( @components ) { - - push @items, 'cust_bill_pkg'; - - push @labels, - ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ). - ( $classnum eq '0' - ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class ) - : '' - ). - ' '.$charge_labels{$component}; - - my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0; - my $row_agentnum = $all_agent || $agent->agentnum; - push @params, [ ($all_class ? () : ('classnum' => $row_classnum) ), - ($all_agent ? () : ('agentnum' => $row_agentnum) ), - 'use_override' => $use_override, - 'charges' => $component, - 'average_per_cust_pkg' => $average_per_cust_pkg, - 'distribute' => $distribute, - ]; - - push @links, "$link;".($all_agent ? '' : "agentnum=$row_agentnum;"). - ($all_class ? '' : "classnum=$row_classnum;"). - "distribute=$distribute;". - "use_override=$use_override;charges=$component;"; - - @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9] - unless @recur_colors; - @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11] - unless @onetime_colors; - push @colors, shift @recur_colors; + foreach my $part_referral ( + $all_part_referral || + $sel_part_referral || + qsearch('part_referral', { 'disabled' => '' } ) + ) { + + foreach my $pkg_class ( @pkg_class ) { + foreach my $component ( @components ) { + + push @items, 'cust_bill_pkg'; + + push @labels, + ( $all_agent || $sel_agent ? '' : $agent->agent.' ' ). + ( $all_part_referral || $sel_part_referral ? '' : $part_referral->referral.' ' ). + ( $classnum eq '0' + ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class ) + : '' + ). + ' '.$charge_labels{$component}; + + my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0; + my $row_agentnum = $all_agent || $agent->agentnum; + my $row_refnum = $all_part_referral || $part_referral->refnum; + push @params, [ ($all_class ? () : ('classnum' => $row_classnum) ), + ($all_agent ? () : ('agentnum' => $row_agentnum) ), + ($all_part_referral ? () : ('refnum' => $row_refnum) ), + 'use_override' => $use_override, + 'charges' => $component, + 'average_per_cust_pkg' => $average_per_cust_pkg, + 'distribute' => $distribute, + ]; + + push @links, "$link;". + ($all_agent ? '' : "agentnum=$row_agentnum;"). + ($all_part_referral ? '' : "refnum=$row_refnum;"). + ($all_class ? '' : "classnum=$row_classnum;"). + "distribute=$distribute;". + "use_override=$use_override;charges=$component;"; + + @recur_colors = ($col_scheme->colors)[0,4,8,1,5,9] + unless @recur_colors; + @onetime_colors = ($col_scheme->colors)[2,6,10,3,7,11] + unless @onetime_colors; + push @colors, shift @recur_colors; + push @no_graph, 0; + + } #foreach $component + } #foreach $pkg_class + } #foreach $part_referral + + if ( $cgi->param('agent_totals') and !$all_agent ) { + my $row_agentnum = $agent->agentnum; + # Include all components that are anywhere on this report + my $component = join('', @components); + + my @row_params = ( 'agentnum' => $row_agentnum, + 'use_override' => $use_override, + 'average_per_cust_pkg' => $average_per_cust_pkg, + 'distribute' => $distribute, + 'charges' => $component, + ); + my $row_link = "$link;". + "agentnum=$row_agentnum;". + "distribute=$distribute;". + "charges=$component"; + + # Also apply any refnum/classnum filters + if ( !$all_class and scalar(@pkg_class) == 1 ) { + # then a specific class has been chosen, but it may be the empty class + my $row_classnum = ref($pkg_class[0]) ? $pkg_class[0]->classnum : 0; + push @row_params, 'classnum' => $row_classnum; + $row_link .= ";classnum=$row_classnum"; } + if ( $sel_part_referral ) { + push @row_params, 'refnum' => $sel_part_referral->refnum; + $row_link .= ";refnum=".$sel_part_referral->refnum; + } + + push @items, 'cust_bill_pkg'; + push @labels, mt('[_1] - Subtotal', $agent->agent); + push @params, \@row_params; + push @links, $row_link; + push @colors, '000000'; # better idea? + push @no_graph, 1; } $hue += $hue_increment; diff --git a/httemplate/graph/elements/monthly.html b/httemplate/graph/elements/monthly.html index 86530dd39..c736de696 100644 --- a/httemplate/graph/elements/monthly.html +++ b/httemplate/graph/elements/monthly.html @@ -20,6 +20,7 @@ Example: 'link_fromparam' => 'param_from', #defaults to 'begin' 'link_toparam' => 'param_to', #defaults to 'end' 'daily' => 1, # omit for monthly granularity + 'no_graph' => \@no_graph, # items to leave off the graph (subtotals) #optional, pulled from CGI params if not specified 'start_month' => $smonth, @@ -35,6 +36,7 @@ Example: #optional 'agentnum' => $agentnum, + 'refnum' => $refnum, 'nototal' => 1, 'graph_type' => 'LinesPoints', 'remove_empty' => 1, @@ -48,18 +50,19 @@ Example: 'items' => $data->{'items'}, 'data' => $data->{'data'}, 'row_labels' => $data->{'item_labels'}, - 'graph_labels' => $opt{'graph_labels'} || $data->{'item_labels'}, + 'graph_labels' => \@graph_labels, 'col_labels' => $col_labels, 'axis_labels' => $data->{label}, - 'colors' => $data->{colors}, + 'colors' => \@colors, 'links' => \@links, + 'no_graph' => \@no_graph, 'bottom_link' => \@bottom_link, 'transpose' => $opt{'daily'}, - map { $_, $opt{$_} } (qw(title - nototal - graph_type - bottom_total - sprintf + map { $_, $opt{$_} } (qw(title + nototal + graph_type + bottom_total + sprintf disable_money chart_options)), ) %> @@ -102,7 +105,7 @@ if ( $opt{'daily'} ) { # daily granularity my %reportopts = ( 'items' => \@items, 'params' => $opt{'params'}, - 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/ + 'item_labels' => ( $cgi->param('_type') =~ /^(png)$/ ? $opt{'graph_labels'} : $opt{'labels'} ), @@ -117,6 +120,7 @@ my %reportopts = ( 'end_year' => $opt{'end_year'}, 'projection' => $opt{'projection'}, 'agentnum' => $opt{'agentnum'}, + 'refnum' => $opt{'refnum'}, 'remove_empty' => $opt{'remove_empty'}, 'doublemonths' => $opt{'doublemonths'}, ); @@ -138,12 +142,20 @@ my $col_labels = [ map { my $m = $_; $m =~ s/^(\d+)\//$mon[$1-1] / ; $m } @{$data->{label}} ]; $col_labels = $data->{label} if $opt{'daily'}; +my @colors; +my @graph_labels; +my @no_graph; if ( $opt{'remove_empty'} ) { - # need to filter out series labels for collapsed rows - $opt{'graph_labels'} = [ - map { $opt{'graph_labels'}[$_] } - @{ $data->{indices} } - ]; + # then filter out per-item things for collapsed rows + foreach my $i (@{ $data->{'indices'} }) { + push @colors, $opt{'colors'}[$i]; + push @graph_labels, $opt{'graph_labels'}[$i]; + push @no_graph, $opt{'no_graph'}[$i]; + } +} else { + @colors = @{ $opt{'colors'} }; + @graph_labels = @{ $opt{'graph_labels'} }; + @no_graph = @{ $opt{'no_graph'} || [] }; } my @links; diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html index 3600f2c66..98b477826 100644 --- a/httemplate/graph/elements/report.html +++ b/httemplate/graph/elements/report.html @@ -14,6 +14,7 @@ Example: 'graph_labels' => \@graph_labels, #defaults to row_labels 'links' => \@links, #optional + 'no_graph' => \@no_graph, #optional #these run parallel to the elements of each @item 'col_labels' => \@col_labels, #required @@ -77,15 +78,16 @@ any delimiter and linked from the elements in @data. % } % % } elsif ( $cgi->param('_type') =~ /(xls)$/ ) { -% -% #http_header('Content-Type' => 'application/excel' ); #eww -% http_header('Content-Type' => 'application/vnd.ms-excel' ); -% #http_header('Content-Type' => 'application/msexcel' ); #alas -% http_header('Content-Disposition' => "attachment;filename=$filename.xls"); +% #false laziness w/ search/elements/search-xls +% my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format; +% $filename .= $format->{extension}; +% +% http_header('Content-Type' => $format->{mime_type} ); +% http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); % % my $output = ''; % my $XLS = new IO::Scalar \$output; -% my $workbook = Spreadsheet::WriteExcel->new($XLS) +% my $workbook = $format->{class}->new($XLS) % or die "Error opening .xls file: $!"; % % my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); @@ -127,7 +129,19 @@ any delimiter and linked from the elements in @data. % <% $output %> % } elsif ( $cgi->param('_type') eq 'png' ) { -% +% # delete any items that shouldn't be on the graph +% if ( my $no_graph = $opt{'no_graph'} ) { +% my $i = 0; +% while (@$no_graph) { +% if ( shift @$no_graph ) { +% splice @data, $i, 1; +% splice @{$opt{'graph_labels'}}, $i, 1; +% splice @{$opt{'colors'}}, $i, 1; +% $i--; # because everything is shifted down +% } +% $i++; +% } +% } % my $graph_type = 'LinesPoints'; % if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain|Bars)$/ ) { % $graph_type = $1; @@ -304,9 +318,6 @@ td.cell { <% include('/elements/footer.html') %> % } -<%once> - -</%once> <%init> my(%opt) = @_; diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index cde71be76..166735fc6 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -1,5 +1,5 @@ <% include('elements/monthly.html', - 'title' => $agentname. + 'title' => $agentname. $referralname. 'Sales, Credits and Receipts Summary', 'items' => \@items, 'labels' => \%label, @@ -7,6 +7,7 @@ 'colors' => \%color, 'links' => \%link, 'agentnum' => $agentnum, + 'refnum' => $refnum, 'nototal' => scalar($cgi->param('12mo')), ) %> @@ -22,9 +23,17 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); die "agentnum $agentnum not found!" unless $agent; } - my $agentname = $agent ? $agent->agent.' ' : ''; +my( $refnum, $part_referral ) = ('', ''); +if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { + $refnum = $1; + $part_referral = qsearchs('part_referral', { 'refnum' => $refnum } ); + die "refnum $refnum not found!" unless $part_referral; +} +my $referralname = $part_referral ? $part_referral->referral.' ' : ''; + + my @items = qw( invoiced netsales credits netcredits payments receipts @@ -83,15 +92,17 @@ my %color = ( $color{$_.'_12mo'} = $color{$_} foreach keys %color; +my $ar = "agentnum=$agentnum;refnum=$refnum"; + my %link = ( - 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;", - 'netsales' => "${p}search/cust_bill.html?agentnum=$agentnum;net=1;", - 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", - 'netcredits' => "${p}search/cust_credit_bill.html?agentnum=$agentnum;", - 'payments' => "${p}search/cust_pay.html?magic=_date;agentnum=$agentnum;", - 'receipts' => "${p}search/cust_bill_pay.html?agentnum=$agentnum;", - 'refunds' => "${p}search/cust_refund.html?magic=_date;agentnum=$agentnum;", - 'netrefunds' => "${p}search/cust_credit_refund.html?agentnum=$agentnum;", + 'invoiced' => "${p}search/cust_bill.html?$ar;", + 'netsales' => "${p}search/cust_bill.html?$ar;net=1;", + 'credits' => "${p}search/cust_credit.html?$ar;", + 'netcredits' => "${p}search/cust_credit_bill.html?$ar;", + 'payments' => "${p}search/cust_pay.html?magic=_date;$ar;", + 'receipts' => "${p}search/cust_bill_pay.html?$ar;", + 'refunds' => "${p}search/cust_refund.html?magic=_date;$ar;", + 'netrefunds' => "${p}search/cust_credit_refund.html?$ar;", ); # XXX link 12mo? diff --git a/httemplate/graph/report_cust_bill_pkg.html b/httemplate/graph/report_cust_bill_pkg.html index 07d4421e8..31792e8dd 100644 --- a/httemplate/graph/report_cust_bill_pkg.html +++ b/httemplate/graph/report_cust_bill_pkg.html @@ -11,20 +11,45 @@ <TD>Show projected data for future months</TD> </TR> -<% include('/elements/tr-select-agent.html', - 'label' => 'For agent: ', - 'disable_empty' => 0, - 'pre_options' => [ 'all' => 'all (aggregate)' ], - 'empty_label' => 'all (breakdown)', - ) -%> - -<% include('/elements/tr-select-pkg_class.html', - 'pre_options' => [ 'all' => 'all (aggregate)', - '0' => 'all (breakdown)' ], - 'empty_label' => '(empty class)', - ) -%> +<SCRIPT TYPE="text/javascript"> +function enable_agent_totals(obj) { +%# enable it iff we are breaking down by agent AND something else + obj.form.agent_totals.disabled = !( + obj.form.agentnum.value == '' && ( + obj.form.refnum.value == '' || + obj.form.classnum.value == 0 || + obj.form.use_setup.value == 1 || + obj.form.use_usage.value == 1 + ) + ); +} +</SCRIPT> + +<& /elements/tr-select-agent.html, + 'field' => 'agentnum', + 'label' => 'Agent ', + 'disable_empty' => 0, + 'pre_options' => [ 'all' => 'all (aggregate)' ], + 'empty_label' => 'all (breakdown)', + 'onchange' => 'enable_agent_totals', +&> + +<& /elements/tr-select-part_referral.html, + 'field' => 'refnum', + 'label' => 'Advertising source ', + 'disable_empty' => 0, + 'pre_options' => [ 'all' => 'all (aggregate)' ], + 'empty_label' => 'all (breakdown)', + 'onchange' => 'enable_agent_totals' +&> + +<& /elements/tr-select-pkg_class.html, + 'field' => 'classnum', + 'pre_options' => [ 'all' => 'all (aggregate)', + '0' => 'all (breakdown)' ], + 'empty_label' => '(empty class)', + 'onchange' => 'enable_agent_totals', +&> <!-- <TR> @@ -39,10 +64,16 @@ 'field' => 'use_'.lc($_), 'options' => [ 0, 1, 2 ], 'labels' => { 0 => 'Combine', 1 => 'Separate', 2 => 'Do not show' }, + 'onchange'=> 'enable_agent_totals', &> % } <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="agent_totals" VALUE="1" DISABLED="1"></TD> + <TD>Show per-agent subtotals</TD> +</TR> + +<TR> <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="use_override" VALUE="1"></TD> <TD>Separate sub-packages from parents</TD> </TR> diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html index b85bb6552..97876c996 100644 --- a/httemplate/graph/report_money_time.html +++ b/httemplate/graph/report_money_time.html @@ -24,6 +24,13 @@ ) %> +<% include('/elements/tr-select-part_referral.html', + 'label' => 'Advertising source ', + 'disable_empty' => 0, + 'empty_label' => 'all', + ) +%> + <TR> <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="12mo" VALUE="1"></TD> <TD>Show 12 month totals instead of monthly values</TD> diff --git a/httemplate/index.html b/httemplate/index.html index ae1509610..71926aa4e 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -1,9 +1,18 @@ +<%init>my $debug = $cgi->param('debug');</%init> +% warn time.": header.html\n" if $debug; +% <& /elements/header.html, mt('Billing Main') &> +% warn time.": dashboard-install_welcome.html\n" if $debug; +% <& /elements/dashboard-install_welcome.html &> +% warn time.": dashboard-toplist.html\n" if $debug; +% <& /elements/dashboard-toplist.html &> +% warn time.": fetching recently changed customers\n" if $debug; +% % my $sth = dbh->prepare( % #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) % "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) @@ -20,6 +29,7 @@ % @custnums = splice(@custnums, 0, 10); % % if ( @custnums ) { +% warn time.": displaying recently changed customers\n" if $debug; <& /elements/table-grid.html &> diff --git a/httemplate/loginout/logout.html b/httemplate/loginout/logout.html index 40371d596..d8e1c634a 100644 --- a/httemplate/loginout/logout.html +++ b/httemplate/loginout/logout.html @@ -8,11 +8,11 @@ <BODY> <BR><BR> <CENTER> - You have logged out + You have logged out. </CENTER> <BR><BR> <CENTER> - Return to <a href="..">freeside</a> + You can <a href="..">log in</a> again. </CENTER> </BODY> </HTML> diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 2e798652d..887b92489 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -1,6 +1,9 @@ -<% include('/elements/header.html', 'Quick payment entry') %> +<& /elements/header.html, { + title => 'Quick payment entry', + etc => 'onload="preload()"' +} &> -<% include('/elements/error.html') %> +<& /elements/error.html &> <SCRIPT TYPE="text/javascript"> function warnUnload() { @@ -14,6 +17,21 @@ function warnUnload() { } window.onbeforeunload = warnUnload; +function add_row_callback(rownum, prefix) { + document.getElementById('enable_app'+rownum).disabled = true; +} + +function custnum_update_callback(rownum, prefix) { + var custnum = document.getElementById('custnum'+rownum).value; + document.getElementById('enable_app'+rownum).disabled = ( + custnum == 0 || + num_open_invoices[rownum] < 2 + ); +% if ( $use_discounts ) { + select_discount_term(rownum, prefix); +% } +} + function select_discount_term(row, prefix) { var custnum_obj = document.getElementById('custnum'+prefix+row); var select_obj = document.getElementById('discount_term'+prefix+row); @@ -46,6 +64,265 @@ function select_discount_term(row, prefix) { discount_terms(custnum_obj.value, select_discount_term_update); } + +var invoices_for_row = new Object; + +function update_invoices(rownum, invoices) { + invoices_for_row[rownum] = new Object; + // only called before create_application_row + for ( var i=0; i<invoices.length; i++ ) { + invoices_for_row[rownum][ invoices[i].invnum ] = invoices[i]; + } +} + +function toggle_application_row(ev, next) { + if (!next) next = function(){}; //optional continuation + var rownum = this.getAttribute('rownum'); + if ( this.checked ) { + var custnum = document.getElementById('custnum'+rownum).value; + if (!custnum) return; + lock_payment_row(rownum, true); + custnum_search_open( custnum, + function(returned) { + update_invoices(rownum, JSON.parse(returned)); + create_application_row(rownum, 0); + next.call(this, rownum); + } + ); + } +} + +function lock_payment_row(rownum, flag) { +% foreach (qw(invnum custnum customer)) { + obj = document.getElementById('<% $_ %>'+rownum); + obj.readOnly = flag; +% } + document.getElementById('enable_app'+rownum).disabled = flag; +} + +function delete_application_row() { + var rownum = this.getAttribute('rownum'); + var appnum = this.getAttribute('appnum'); + var tr_app = document.getElementById('row'+rownum+'.'+appnum); + var select_invnum = document.getElementById('invnum'+rownum+'.'+appnum); + if ( select_invnum.value ) { + invoices_for_row[rownum][ select_invnum.value ] = select_invnum.curr_invoice; + } + + tr_app.parentNode.removeChild(tr_app); + if ( appnum > 0 ) { + document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = ''; + } + else { + lock_payment_row(rownum, false); + document.getElementById('enable_app'+rownum).checked = false; + } +} + +function amount_unapplied(rownum) { + var appnum = 0; + var total = 0; + var payment_amount = parseFloat(document.getElementById('paid'+rownum).value) + || 0; + while (true) { + var input_amount = document.getElementById('amount'+rownum+'.'+appnum); + if ( input_amount ) { + total += parseFloat(input_amount.value || 0); + appnum++; + } + else { + return payment_amount - total; + } + } +} + +var change_app_amount; + +function choose_app_invnum() { + var rownum = this.getAttribute('rownum'); + var appnum = this.getAttribute('appnum'); + var last_invoice = this.curr_invoice; + if ( last_invoice ) { + invoices_for_row[rownum][ last_invoice['invnum'] ] = last_invoice; + } + + if ( this.value ) { + var this_invoice = invoices_for_row[rownum][this.value]; + this.curr_invoice = invoices_for_row[rownum][this.value]; + var span_owed = document.getElementById('owed'+rownum+'.'+appnum); + span_owed.innerHTML = this_invoice['owed'] + ' '; + delete invoices_for_row[rownum][this.value]; + + var input_amount = document.getElementById('amount'+rownum+'.'+appnum); + if ( input_amount.value == '' ) { + input_amount.value = + Math.max( + 0, Math.min( amount_unapplied(rownum), this_invoice['owed']) + ).toFixed(2); + // trigger onchange + change_app_amount.call(input_amount); + } + } +} + +function focus_app_invnum() { +% # invoice numbers just display as invoice numbers + var rownum = this.getAttribute('rownum'); + var add_opt = function(obj, value) { + var o = document.createElement('OPTION'); + o.text = value; + o.value = value; + obj.add(o); + } + this.options.length = 0; + var this_invoice = this.curr_invoice; + if ( this_invoice ) { + add_opt(this, this_invoice.invnum); + } else { + add_opt(this, ''); + } + for ( var x in invoices_for_row[rownum] ) { + add_opt(this, invoices_for_row[rownum][x].invnum); + } +} + +function change_app_amount() { + var rownum = this.getAttribute('rownum'); + var appnum = this.getAttribute('appnum'); +%# maybe some kind of warning if amount_unapplied < 0? +%# only spawn a new application row if there are open invoices left, +%# and this is the highest-numbered application row for the customer, +%# and the sum of the applied amounts is < the amount of the payment, + if ( Object.keys(invoices_for_row[rownum]).length > 0 + && !document.getElementById( 'row'+rownum+'.'+(parseInt(appnum) + 1) ) + && amount_unapplied(rownum) > 0 ) { + + create_application_row(rownum, parseInt(appnum) + 1); + + } +} + +function create_application_row(rownum, appnum) { + var payment_row = document.getElementById('row'+rownum); + var tr_app = document.createElement('TR'); + tr_app.setAttribute('rownum', rownum); + tr_app.setAttribute('appnum', appnum); + tr_app.setAttribute('id', 'row'+rownum+'.'+appnum); + + var td_invnum = document.createElement('TD'); + td_invnum.setAttribute('colspan', 4); + td_invnum.style.textAlign = 'right'; + td_invnum.appendChild( + document.createTextNode('<% mt('Apply to Invoice ') %>') + ); + var select_invnum = document.createElement('SELECT'); + select_invnum.setAttribute('rownum', rownum); + select_invnum.setAttribute('appnum', appnum); + select_invnum.setAttribute('id', 'invnum'+rownum+'.'+appnum); + select_invnum.setAttribute('name', 'invnum'+rownum+'.'+appnum); + select_invnum.style.textAlign = 'right'; + select_invnum.style.width = '50px'; + select_invnum.onchange = choose_app_invnum; + select_invnum.onfocus = focus_app_invnum; + + td_invnum.appendChild(select_invnum); + tr_app.appendChild(td_invnum); + + var td_owed = document.createElement('TD'); + td_owed.style.textAlign= 'right'; + var span_owed = document.createElement('SPAN'); + span_owed.setAttribute('rownum', rownum); + span_owed.setAttribute('appnum', appnum); + span_owed.setAttribute('id', 'owed'+rownum+'.'+appnum); + td_owed.appendChild(span_owed); + tr_app.appendChild(td_owed); + + var td_amount = document.createElement('TD'); + td_amount.style.textAlign = 'right'; + var input_amount = document.createElement('INPUT'); + input_amount.size = 6; + input_amount.setAttribute('rownum', rownum); + input_amount.setAttribute('appnum', appnum); + input_amount.setAttribute('name', 'amount'+rownum+'.'+appnum); + input_amount.setAttribute('id', 'amount'+rownum+'.'+appnum); + input_amount.style.textAlign = 'right'; + input_amount.onchange = change_app_amount; + td_amount.appendChild(input_amount); + tr_app.appendChild(td_amount); + + var td_delete = document.createElement('TD'); + td_delete.setAttribute('colspan', <% scalar(@fields)-2 %>); + var button_delete = document.createElement('INPUT'); + button_delete.setAttribute('rownum', rownum); + button_delete.setAttribute('appnum', appnum); + button_delete.setAttribute('id', 'delete'+rownum+'.'+appnum); + button_delete.setAttribute('type', 'button'); + button_delete.setAttribute('value', 'X'); + button_delete.onclick = delete_application_row; + button_delete.style.color = '#ff0000'; + button_delete.style.fontWeight = 'bold'; + button_delete.style.paddingLeft = '2px'; + button_delete.style.paddingRight = '2px'; + td_delete.appendChild(button_delete); + tr_app.appendChild(td_delete); + + var td_error = document.createElement('TD'); + var span_error = document.createElement('SPAN'); + span_error.setAttribute('rownum', rownum); + span_error.setAttribute('appnum', appnum); + span_error.setAttribute('id', 'error'+rownum+'.'+appnum); + span_error.style.color = '#ff0000'; + td_error.appendChild(span_error); + tr_app.appendChild(td_error); + + if ( appnum > 0 ) { + //remove delete button on the previous row + document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = 'none'; + } + rownum++; + var next_row = document.getElementById('row'+rownum); // always exists + payment_row.parentNode.insertBefore(tr_app, next_row); + +} + +%# for error handling--ugly, but the alternative is translating the whole +%# process of creating rows into Mason +var row_array = <% encode_json(\@rows) %>; +function preload() { + var rownum; + var appnum; + for (rownum=0; rownum < row_array.length; rownum++) { + if ( row_array[rownum].length ) { + var enable = document.getElementById('enable_app'+rownum); + enable.checked = true; + var preload_row = function(r) {//continuation from toggle_application_row + for (appnum=0; appnum < row_array[r].length; appnum++) { + this_app = row_array[r][appnum]; + var x = r + '.' + appnum; + //set invnum + var select_invnum = document.getElementById('invnum'+x); + focus_app_invnum.call(select_invnum); + for (i=0; i<select_invnum.options.length; i++) { + if (select_invnum.options[i].value == this_app.invnum) { + select_invnum.selectedIndex = i; + } + } + choose_app_invnum.call(select_invnum); + //set amount + var input_amount = document.getElementById('amount'+x); + input_amount.value = this_app.amount; + + //set error + var span_error = document.getElementById('error'+x); + span_error.innerHTML = this_app.error; + change_app_amount.call(input_amount); //creates next row + } //for appnum + }; //preload_row function + toggle_application_row.call(enable, null, preload_row); + } // if row_array[rownum].length + } //for rownum +} + </SCRIPT> <% include('/elements/xmlhttp.html', @@ -57,21 +334,26 @@ function select_discount_term(row, prefix) { <FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.btnsubmit.disabled=true;window.onbeforeunload = null;"> <!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> --> +<& /elements/xmlhttp.html, + url => $p.'misc/xmlhttp-cust_bill-search.html', + subs => ['custnum_search_open'] +&> -<% include( "/elements/customer-table.html", - name_singular => 'payment', - header => \@header, - fields => \@fields, - type => \@types, - align => \@align, - size => \@sizes, - color => \@colors, - param => \%param, - footer => \@footer, - footer_align => \@footer_align, - custnum_update_callback => $custnum_update_callback, - ) -%> +<& /elements/customer-table.html, + name_singular => 'payment', + header => \@header, + fields => \@fields, + type => \@types, + align => \@align, + size => \@sizes, + color => \@colors, + param => \%param, + footer => \@footer, + footer_align => \@footer_align, + onchange => \@onchange, + custnum_update_callback => 'custnum_update_callback', + add_row_callback => 'add_row_callback', +&> <BR> <INPUT TYPE="button" VALUE="Post payment batch" name="btnsubmit" onclick="window.onbeforeunload = null; document.OneTrueForm.submit(); this.disabled = true;"> @@ -105,7 +387,8 @@ my @colors = ( '', '' ); my %param = (); my @footer = ( '_TOTAL', '' ); my @footer_align = ( 'r', 'r' ); -my $custnum_update_callback = ''; +my @onchange = ( '', '' );; +my $use_discounts = ''; if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { #push @header, 'Discount'; @@ -117,9 +400,20 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) { push @colors, ''; push @footer, ''; push @footer_align, ''; - $custnum_update_callback = 'select_discount_term'; + push @onchange, ''; + $use_discounts = 'Y'; } +push @header, 'Allocate'; +push @fields, 'enable_app'; +push @types, 'checkbox'; +push @align, 'c'; +push @sizes, '0'; +push @colors, ''; +push @footer, ''; +push @footer_align, ''; +push @onchange, 'toggle_application_row'; + #push @header, 'Error'; push @header, ''; push @fields, 'error'; @@ -129,7 +423,34 @@ push @sizes, '0'; push @colors, '#ff0000'; push @footer, ''; push @footer_align, ''; +push @onchange, ''; $m->comp('/elements/handle_uri_query'); +# set up for preloading +my @rows; +my @row_errors; +if ( $cgi->param('error') ) { + my $param = $cgi->Vars; + my $enum = 0; #errors numbered separately + for( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { + $rows[$row] = []; + $row_errors[$row] = $param->{"error$enum"}; + $enum++; + for( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) { + next if !$param->{"invnum$row.$app"}; + my %this_app = map { $_ => ($param->{$_.$row.'.'.$app} || '') } + qw( invnum amount ); + $this_app{'error'} = $param->{"error$enum"} || ''; + $param->{"error$enum"} = ''; # don't pass this error through + $rows[$row][$app] = \%this_app; + $enum++; + } + } + for( my $row = 0; $row < @row_errors; $row++ ) { + $param->{"error$row"} = $row_errors[$row]; + } +} +#warn Dumper {rows => \@rows, row_errors => \@row_errors }; + </%init> diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index 4b5df8654..f9a46a898 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -13,50 +13,94 @@ % my $date_init = 0; % if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') { % $submit =~ /^(\w*)\s/; -<& /elements/tr-input-date-field.html, { - 'name' => 'date', - 'value' => $date, - 'label' => mt("$1 package on"), - 'format' => $date_format, -} &> + <& /elements/tr-input-date-field.html, { + 'name' => 'date', + 'value' => $date, + 'label' => mt("$1 package on"), + 'format' => $date_format, + } &> % $date_init = 1; % } -% unless ( $method eq 'resume' ) { #the only one that doesn't need a reason -<& /elements/tr-select-reason.html, - 'field' => 'reasonnum', - 'reason_class' => $class, - 'curr_value' => $reasonnum, - 'control_button' => "document.getElementById('confirm_cancel_pkg_button')", -&> +% if ($method eq 'uncancel' ) { +% +% #XXX customer also requested setup +% # setup: what usefulness is changing or blanking this? re-charge setup fee? +% # an option that says that would be better if that's what we want to do + +% # last_bill: isn't this informational? what good would editing it do? +% # something about invoice display? + <& /elements/tr-input-date-field.html, { + 'name' => 'last_bill', + 'value' => ( $cgi->param('last_bill') || $cust_pkg->get('last_bill') ), + 'label' => mt("Last bill date"), + 'format' => $date_format, + } &> + + <& /elements/tr-input-date-field.html, { + 'name' => 'bill', + 'value' => ( $cgi->param('bill') || $cust_pkg->get('bill') ), + 'label' => mt("Next bill date"), + 'format' => $date_format, + } &> + + <& /elements/tr-checkbox.html, + 'label' => mt("Uncancel even if a service can't be re-provisioned"), + 'field' => 'svc_not_fatal', + 'value' => 'Y', + &> + +% $date_init = 1; +% } + +% unless ( $method eq 'resume' || $method eq 'uncancel' ) { + <& /elements/tr-select-reason.html, + field => 'reasonnum', + reason_class => $class, + curr_value => $reasonnum, + control_button => "document.getElementById('confirm_cancel_pkg_button')", + &> % } -% if ( ( $method eq 'adjourn' or $method eq 'suspend' ) and +% if ( $method eq 'adjourn' || $method eq 'suspend' ) { + <TR><TD COLSPAN=2> +% if ( $part_pkg->option('suspend_bill', 1) ) { + <& /elements/checkbox.html, name=>'no_suspend_bill', value=>'Y' &> + Disable recurring billing while suspended +% } else { + <& /elements/checkbox.html, name=>'suspend_bill', value=>'Y' &> + Continue recurring billing while suspended +% } + </TD></TR> +% } + +% if ( ( $method eq 'adjourn' || $method eq 'suspend' ) and % $curuser->access_right('Unsuspend customer package') ) { #later? % my $resume_date = $cgi->param('error') % ? str2time($cgi->param('resume_date')) % : $cust_pkg->get('resume'); -<& /elements/tr-input-date-field.html, { - 'name' => 'resume_date', - 'value' => $resume_date, - 'label' => mt('Unsuspend on'), - 'format' => $date_format, - 'noinit' => $date_init, -} &> + <& /elements/tr-input-date-field.html, { + 'name' => 'resume_date', + 'value' => $resume_date, + 'label' => mt('Unsuspend on'), + 'format' => $date_format, + 'noinit' => $date_init, + } &> % } </TABLE> <BR> <INPUT TYPE="submit" NAME="submit" ID="confirm_cancel_pkg_button" VALUE="<% mt($submit) |h %>" - <% $method ne 'resume' ? 'DISABLED' : '' %>> + <% $method !~ /^(resume|uncancel)$/ ? 'DISABLED' : '' %>> </FORM> </BODY> </HTML> <%init> +use Date::Parse qw(str2time); my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; @@ -99,6 +143,10 @@ if ($method eq 'cancel') { $class = ''; $submit = 'Unsuspend Later'; $right = 'Unsuspend customer package'; #later? +} elsif ( $method eq 'uncancel') { + $class = ''; + $submit = 'Un-Cancel'; + $right = 'Un-cancel customer package'; #later? } else { die 'illegal query (unknown method param)'; } @@ -107,6 +155,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right($right); my $title = ucfirst($method) . ' Package'; +$title =~ s/Uncancel/Un-cancel/; my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}) or die "Unknown pkgnum: $pkgnum"; diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi index dcd033ff2..a277ba407 100644 --- a/httemplate/misc/cust-part_pkg.cgi +++ b/httemplate/misc/cust-part_pkg.cgi @@ -1,11 +1,19 @@ <% objToJson( \@return ) %> <%init> -my( $custnum, $classnum ) = $cgi->param('arg'); +my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg'); -#XXX i guess i should be agent-virtualized. cause "packages a customer can -#order" is such a huge deal -my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); + +my $agent; +if ( $custnum ) { + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or die 'unknown custnum'; + $agent = $cust_main->agent; +} else { + my $prospect_main = qsearchs('prospect_main', {'prospectnum'=>$prospectnum} ) + or die 'unknown prospectnum'; + $agent = $prospect_main->agent; +} my %hash = ( 'disabled' => '' ); if ( $classnum > 0 ) { @@ -19,7 +27,7 @@ my @part_pkg = qsearch({ 'hashref' => \%hash, 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql( 'null'=>1 ). - ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ), + ' AND '. FS::part_pkg->agent_pkgs_sql( $agent ), 'order_by' => 'ORDER BY pkg', }); diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi index 002c0011a..2ae9f1021 100755 --- a/httemplate/misc/cust_main-cancel.cgi +++ b/httemplate/misc/cust_main-cancel.cgi @@ -1,4 +1,4 @@ -<& /elements/header.html, mt("Customer cancelled") &> +<& /elements/header-popup.html, mt("Customer cancelled") &> <SCRIPT TYPE="text/javascript"> window.top.location.reload(); </SCRIPT> diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi index 664651069..d56feaccf 100644 --- a/httemplate/misc/cust_main-import.cgi +++ b/httemplate/misc/cust_main-import.cgi @@ -35,6 +35,8 @@ Import a file containing customer records. <OPTION VALUE="extended-plus_company_and_options">Extended plus company and options <OPTION VALUE="svc_external">External service <OPTION VALUE="svc_external_svc_phone">External service and phone service + <OPTION VALUE="birthdates-acct_phone_hardware">Birthdates and account, phone and hardware services + <OPTION VALUE="national_id-acct_phone">National ID, plus account and phone services </SELECT> </TD> </TR> @@ -106,6 +108,12 @@ Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. <b>External service and phone service</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, id, title, countrycode, phonenum, sip_password, pin</i> <BR><BR> +<b>Birthdates and account, phone and hardware services</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, birthdate, spouse_birthdate, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, username, _password, countrycode, phonenum, sip_password, pin, typenum, ip_addr, hw_addr, serial</i> +<BR><BR> + +<b>National ID, plus account and phone services</b> format has the following field order: <i>agent_custid, refnum<%$req%>, last<%$req%>, first<%$req%>, company, address1<%$req%>, address2, city<%$req%>, state<%$req%>, zip<%$req%>, country, daytime, night, ship_last, ship_first, ship_company, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, national_id, payinfo, paycvv, paydate, invoicing_list, pkgpart, next_bill_date, username, _password, slipip, countrycode, phonenum, sip_password, pin</i> +<BR><BR> + <%$req%> Required fields <BR><BR> @@ -131,6 +139,8 @@ advertising source table. <li><i>username</i> and <i>_password</i> are required if <i>pkgpart</i> is specified. (Extended and Extended plus company formats) + <li><i>slipip</i>: IP address + <li><i>id</i>: External service id, integer <li><i>title</i>: External service identifier, text diff --git a/httemplate/misc/cust_main-suspend.cgi b/httemplate/misc/cust_main-suspend.cgi new file mode 100755 index 000000000..61851364e --- /dev/null +++ b/httemplate/misc/cust_main-suspend.cgi @@ -0,0 +1,75 @@ +<& /elements/header-popup.html, mt("Customer suspended") &> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY> +</HTML> +<%init> + +#false laziness w/cust_main-cancel.cgi + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Suspend customer'); + +my $custnum; +my $adjourn = ''; +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $custnum = $1; + $adjourn = $cgi->param('adjourn'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/ || die "Illegal custnum"; + $custnum = $1; +} + +#false laziness w/process/cancel_pkg.html + +#untaint reasonnum +my $reasonnum = $cgi->param('reasonnum'); +$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum"; +$reasonnum = $1; + +if ($reasonnum == -1) { + $reasonnum = { + 'typenum' => scalar( $cgi->param('newreasonnumT') ), + 'reason' => scalar( $cgi->param('newreasonnum' ) ), + }; +} + +#eslaf + +my $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +} ); + +my @errors; +if($cgi->param('now_or_later')) { + $adjourn = parse_datetime($adjourn); + if($adjourn) { + #warn "setting adjourn dates on custnum#$custnum\n"; + my @pkgs = $cust_main->unsuspended_pkgs; + @errors = grep {$_} map { $_->suspend( + 'reason' => $reasonnum, + 'date' => $adjourn, + ) } @pkgs; + } + else { + @errors = ("error parsing adjourn date: ".$cgi->param('adjourn')); + } +} +else { + warn "suspending $cust_main"; + @errors = $cust_main->suspend( + 'reason' => $reasonnum, + ); +} +my $error = join(' / ', @errors) if scalar(@errors); + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(1). "suspend_cust.html?". $cgi->query_string ); +} + +</%init> diff --git a/httemplate/misc/cust_main-unsuspend.cgi b/httemplate/misc/cust_main-unsuspend.cgi new file mode 100755 index 000000000..eb4a2c8f8 --- /dev/null +++ b/httemplate/misc/cust_main-unsuspend.cgi @@ -0,0 +1,56 @@ +<& /elements/header-popup.html, mt("Customer unsuspended") &> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY> +</HTML> +<%init> + +#false laziness w/cust_main-cancel.cgi + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Unsuspend customer'); + +my $custnum; +my $resume = ''; +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $custnum = $1; + $resume = $cgi->param('resume'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/ || die "Illegal custnum"; + $custnum = $1; +} + +my $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +} ); + +my @errors; +if($cgi->param('now_or_later')) { + $resume = parse_datetime($resume); + if($resume) { + #warn "setting resume dates on custnum#$custnum\n"; + my @pkgs = $cust_main->suspended_pkgs; + @errors = grep {$_} map { $_->unsuspend( + 'date' => $resume, + ) } @pkgs; + } + else { + @errors = ("error parsing adjourn date: ".$cgi->param('adjourn')); + } +} +else { + warn "unsuspending $cust_main"; + @errors = $cust_main->unsuspend; +} +my $error = join(' / ', @errors) if scalar(@errors); + +if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(1). "unsuspend_cust.html?". $cgi->query_string ); +} + +</%init> diff --git a/httemplate/misc/delete-ftp_target.html b/httemplate/misc/delete-ftp_target.html new file mode 100644 index 000000000..c8bd29701 --- /dev/null +++ b/httemplate/misc/delete-ftp_target.html @@ -0,0 +1,18 @@ +% if ( $error ) { +% errorpage($error); +% } else { +<% $cgi->redirect("${p}browse/ftp_target.html") %> +% } +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/ || die "Illegal targetnum"; +my $targetnum = $1; + +my $target = qsearchs('ftp_target',{'targetnum'=>$targetnum}); +my $error = $target->delete; + +</%init> diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 23deba712..f3a31eb3b 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -1,4 +1,4 @@ -<% $pay_batch->export_batch($format) %><%init> +<% $pay_batch->export_batch(%opt) %><%init> #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); # not necessarily correct... @@ -10,9 +10,14 @@ if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) { die "No batch number (bad URL) \n"; } -my $format; -if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { - $format = $1; +my %opt; +if ( $cgi->param('gatewaynum') =~ /^(\d+)$/ ) { + my $gateway = FS::payment_gateway->by_key($1); + die "gatewaynum $1 not found" unless $gateway; + $opt{'gateway'} = $gateway; +} +elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { + $opt{'format'} = $1; } my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } ); diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index 66717b5bf..bfc7b6903 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -1,4 +1,6 @@ -<& /elements/header-popup.html, mt('Order new package') &> +<& /elements/header-popup.html, $quotationnum ? mt('Add package to quotation') + : mt('Order new package') +&> <LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> @@ -11,8 +13,10 @@ <FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST"> -<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main ? $cust_main->custnum : '' %>"> +<INPUT TYPE="hidden" NAME="prospectnum" VALUE="<% $prospect_main ? $prospect_main->prospectnum : '' %>"> <INPUT TYPE="hidden" NAME="qualnum" VALUE="<% scalar($cgi->param('qualnum')) |h %>"> +<INPUT TYPE="hidden" NAME="quotationnum" VALUE="<% $quotationnum %>"> % if ( $svcpart ) { <INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>"> % } @@ -26,12 +30,24 @@ </TR> % } else { <& /elements/tr-select-cust-part_pkg.html, - 'curr_value' => $pkgpart, - 'classnum' => -1, - 'cust_main' => $cust_main, + 'curr_value' => $pkgpart, + 'classnum' => -1, + 'cust_main' => $cust_main, + 'prospect_main' => $prospect_main, &> % } +% if ( $conf->exists('invoice-unitprice') ) { + <TR> + <TH ALIGN="right"><% mt('Quantity') |h %> </TD> + <TD> + <INPUT TYPE="text" NAME="quantity" SIZE=4 VALUE="<% $quantity %>"> + </TD> + </TR> +% } else { + <INPUT TYPE="hidden" NAME="quantity" VALUE="1"> +% } + <TR> <TH ALIGN="right"><% mt('Start date') |h %> </TD> <TD COLSPAN=6> @@ -45,7 +61,7 @@ </TD> </TR> -% if ( $cust_main->payby =~ /^(CARD|CHEK)$/ ) { +% if ( $cust_main && $cust_main->payby =~ /^(CARD|CHEK)$/ ) { % my $what = lc(FS::payby->shortname($cust_main->payby)); <TR> <TH ALIGN="right"><% mt("Disable automatic $what charge") |h %> </TH> @@ -88,8 +104,9 @@ % } else { <& /elements/tr-select-cust_location.html, - 'cgi' => $cgi, - 'cust_main' => $cust_main, + 'cgi' => $cgi, + 'cust_main' => $cust_main, + 'prospect_main' => $prospect_main, &> % } @@ -144,29 +161,56 @@ die "access denied" my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; -$cgi->param('custnum') =~ /^(\d+)$/ or die "no custnum"; -my $custnum = $1; -my $cust_main = qsearchs({ - 'table' => 'cust_main', - 'hashref' => { 'custnum' => $custnum }, - 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -}); +my $cust_main = ''; +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + my $custnum = $1; + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, + }); +} + +my $prospect_main = ''; +if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { + my $prospectnum = $1; + $prospect_main = qsearchs({ + 'table' => 'prospect_main', + 'hashref' => { 'prospectnum' => $prospectnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, + }); +} + +my $quotationnum = ''; +if ( $cgi->param('quotationnum') =~ /^(\d+)$/ ) { + $quotationnum = $1; +} + +die 'no custnum or prospectnum' unless $cust_main || $prospect_main; my $part_pkg = ''; if ( $cgi->param('lock_pkgpart') ) { $part_pkg = qsearchs({ 'table' => 'part_pkg', 'hashref' => { 'pkgpart' => scalar($cgi->param('lock_pkgpart')) }, - 'extra_sql' => ' AND '. FS::part_pkg->agent_pkgs_sql( $cust_main->agent ), + 'extra_sql' => ' AND '. FS::part_pkg->agent_pkgs_sql( + $cust_main ? $cust_main->agent + : $prospect_main->agent + ), }) or die "unknown pkgpart ". $cgi->param('lock_pkgpart'); } my $pkgpart = $part_pkg ? $part_pkg->pkgpart : scalar($cgi->param('pkgpart')); +my $quantity = 1; +if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) { + $quantity = $1; +} + my $format = $date_format. ' %T %z (%Z)'; #false laziness w/REAL_cust_pkg.cgi? my $start_date = ''; -if( ! $conf->exists('order_pkg-no_start_date') ) { +if( ! $conf->exists('order_pkg-no_start_date') && $cust_main ) { $start_date = $cust_main->next_bill_date; $start_date = $start_date ? time2str($format, $start_date) : ''; } diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 093494a06..5b9f63dc0 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -9,67 +9,21 @@ <& /elements/init_overlib.html &> <% ntable('#cccccc') %> - <TR> - <TH ALIGN="right"><% mt('Payment amount') |h %></TH> - <TD COLSPAN=7> - <TABLE><TR><TD BGCOLOR="#ffffff"> - <% $money_char %><INPUT NAME = "amount" - ID = "amount" - TYPE = "text" - VALUE = "<% $amount %>" - SIZE = 8 - STYLE = "text-align:right;" -% if ( $fee ) { - onChange = "amount_changed(this)" - onKeyDown = "amount_changed(this)" - onKeyUp = "amount_changed(this)" - onKeyPress = "amount_changed(this)" -% } - > - </TD><TD BGCOLOR="#cccccc"> -% if ( $fee ) { - <INPUT TYPE="hidden" NAME="fee_pkgpart" VALUE="<% $fee_pkg->pkgpart %>"> - <INPUT TYPE="hidden" NAME="fee" VALUE="<% $fee_display eq 'add' ? $fee : '' %>"> - <B><FONT SIZE='+1'><% $fee_op %></FONT> - <% $money_char . $fee %> - </B> - <% $fee_pkg->pkg |h %> - <B><FONT SIZE='+1'>=</FONT></B> - </TD><TD ID="ajax_total_cell" BGCOLOR="#dddddd" STYLE="border:1px solid blue"> - <FONT SIZE="+1"><% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT> - -% } - </TD></TR></TABLE> - </TD> - </TR> - -% if ( $fee ) { - - <SCRIPT TYPE="text/javascript"> - - function amount_changed(what) { - - - var total = ''; - if ( what.value.length ) { - total = parseFloat(what.value) <% $fee_op %> <% $fee %>; - /* total = Math.round(total*100)/100; */ - total = '<% $money_char %>' + total.toFixed(2); - } - - var total_cell = document.getElementById('ajax_total_cell'); - total_cell.innerHTML = '<FONT SIZE="+1">' + total + ' <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %></FONT>'; - - } - - </SCRIPT> -% } - -<& /elements/tr-select-discount_term.html, - 'custnum' => $custnum, - 'amount_id' => 'amount', -&> + <& /elements/tr-amount_fee.html, + 'amount' => $amount, + 'process-pkgpart' => + scalar($conf->config('manual_process-pkgpart', $cust_main->agentnum)), + 'process-display' => scalar($conf->config('manual_process-display')), + 'process-skip_first' => $conf->exists('manual_process-skip_first'), + 'num_payments' => scalar($cust_main->cust_pay), + 'surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')), + &> + + <& /elements/tr-select-discount_term.html, + 'custnum' => $custnum, + 'amount_id' => 'amount', + &> % if ( $payby eq 'CARD' ) { % @@ -125,7 +79,7 @@ </TR> <& /elements/location.html, - 'object' => $cust_main, #XXX errors??? + 'object' => $cust_main->bill_location, 'no_asterisks' => 1, 'address1_label' => emt('Card billing address'), &> @@ -298,14 +252,16 @@ my $custnum = $1; my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); die "unknown custnum $custnum" unless $cust_main; +my $location = $cust_main->bill_location; +# no proper error handling on this anyway, but when we have it, +# remember to repopulate fields in $location + my $balance = $cust_main->balance; my $payinfo = ''; my $conf = new FS::Conf; -my $money_char = $conf->config('money_char') || '$'; - #false laziness w/selfservice make_payment.html shortcut for one-country my %states = map { $_->state => 1 } qsearch('cust_main_county', { @@ -313,42 +269,9 @@ my %states = map { $_->state => 1 } } ); my @states = sort { $a cmp $b } keys %states; -my $fee = ''; -my $fee_pkg = ''; -my $fee_display = ''; -my $fee_op = ''; -my $num_payments = scalar($cust_main->cust_pay); -#handle old cust_main.pm (remove...) -$num_payments = scalar( @{ [ $cust_main->cust_pay ] } ) - unless defined $num_payments; -if ( $conf->config('manual_process-pkgpart') - and ! $conf->exists('manual_process-skip_first') || $num_payments - ) -{ - - $fee_display = $conf->config('manual_process-display') || 'add'; - $fee_op = $fee_display eq 'add' ? '+' : '-'; - - $fee_pkg = - qsearchs('part_pkg', { pkgpart=>$conf->config('manual_process-pkgpart') } ); - - #well ->unit_setup or ->calc_setup both call for a $cust_pkg - # (though ->unit_setup doesn't use it...) - $fee = $fee_pkg->option('setup_fee') - if $fee_pkg; #in case.. better than dying with a perl traceback - -} - my $amount = ''; if ( $balance > 0 ) { $amount = $balance; - $amount += $fee - if $fee && $fee_display eq 'subtract'; - - my $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage'); - $amount += $amount * $cc_surcharge_pct/100 if $cc_surcharge_pct > 0; - - $amount = sprintf("%.2f", $amount); } my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32; diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi index a6b90ea74..1105af943 100644 --- a/httemplate/misc/process/batch-cust_pay.cgi +++ b/httemplate/misc/process/batch-cust_pay.cgi @@ -1,51 +1,69 @@ -% die "access denied" -% unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch'); -% -% my $param = $cgi->Vars; -% -% #my $paybatch = $param->{'paybatch'}; -% my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); -% -% my @cust_pay = (); -% #my $row = 0; -% #while ( exists($param->{"custnum$row"}) ) { -% for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { -% my $custnum = $param->{"custnum$row"}; -% my $cust_main; -% if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) { -% $cust_main = qsearchs({ -% 'table' => 'cust_main', -% 'hashref' => { 'custnum' => $1 }, -% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -% }); -% } -% if ( length($custnum) and !$cust_main ) { # not found, try agent_custid -% $cust_main = qsearchs({ -% 'table' => 'cust_main', -% 'hashref' => { 'agent_custid' => $custnum }, -% 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, -% }); -% } -% $custnum = $cust_main->custnum if $cust_main; -% # if !$cust_main, then this will throw an error on batch_insert -% -% push @cust_pay, new FS::cust_pay { -% 'custnum' => $custnum, -% 'paid' => $param->{"paid$row"}, -% 'payby' => 'BILL', -% 'payinfo' => $param->{"payinfo$row"}, -% 'discount_term' => $param->{"discount_term$row"}, -% 'paybatch' => $paybatch, -% } -% if $param->{"custnum$row"} -% || $param->{"paid$row"} -% || $param->{"payinfo$row"}; -% #$row++; -% } -% -% my @errors = FS::cust_pay->batch_insert(@cust_pay); -% my $num_errors = scalar(grep $_, @errors); -% +<%init> +my $DEBUG = 0; +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch'); + +my $param = $cgi->Vars; +warn Dumper($param) if $DEBUG; + +#my $paybatch = $param->{'paybatch'}; +my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); + +my @cust_pay = (); +#my $row = 0; +#while ( exists($param->{"custnum$row"}) ) { +for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) { + my $custnum = $param->{"custnum$row"}; + my $cust_main; + if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) { + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $1 }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); + } + if ( length($custnum) and !$cust_main ) { # not found, try agent_custid + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'agent_custid' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); + } + $custnum = $cust_main->custnum if $cust_main; + # if !$cust_main, then this will throw an error on batch_insert + + my $cust_pay = new FS::cust_pay { + 'custnum' => $custnum, + 'paid' => $param->{"paid$row"}, + 'payby' => 'BILL', + 'payinfo' => $param->{"payinfo$row"}, + 'discount_term' => $param->{"discount_term$row"}, + 'paybatch' => $paybatch, + } + if $param->{"custnum$row"} + || $param->{"paid$row"} + || $param->{"payinfo$row"}; + next if !$cust_pay; + #$row++; + + # payment applications, if any + my @cust_bill_pay = (); + for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) { + next if !$param->{"invnum$row.$app"}; + push @cust_bill_pay, new FS::cust_bill_pay { + 'invnum' => $param->{"invnum$row.$app"}, + 'amount' => $param->{"amount$row.$app"} + }; + } + $cust_pay->set('apply_to', \@cust_bill_pay) if scalar(@cust_bill_pay) > 0; + + push @cust_pay, $cust_pay; + +} + +my @errors = FS::cust_pay->batch_insert(@cust_pay); +my $num_errors = scalar(grep $_, @errors); +</%init> % if ( $num_errors ) { % % $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : ''). @@ -65,4 +83,3 @@ % <% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %> % } - diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index 662a77670..a106b845a 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -6,19 +6,21 @@ </HTML> <%once> -my %past = ( 'cancel' => 'cancelled', - 'expire' => 'expired', - 'suspend' => 'suspended', - 'adjourn' => 'adjourned', - 'resume' => 'scheduled to resume', +my %past = ( 'cancel' => 'cancelled', + 'expire' => 'expired', + 'suspend' => 'suspended', + 'adjourn' => 'adjourned', + 'resume' => 'scheduled to resume', + 'uncancel' => 'un-cancelled', ); #i'm sure this is false laziness with somewhere, at least w/misc/cancel_pkg.html -my %right = ( 'cancel' => 'Cancel customer package immediately', - 'expire' => 'Cancel customer package later', - 'suspend' => 'Suspend customer package', - 'adjourn' => 'Suspend customer package later', - 'resume' => 'Unsuspend customer package', #later? +my %right = ( 'cancel' => 'Cancel customer package immediately', + 'expire' => 'Cancel customer package later', + 'suspend' => 'Suspend customer package', + 'adjourn' => 'Suspend customer package later', + 'resume' => 'Unsuspend customer package', #later? + 'uncancel' => 'Un-cancel customer package', ); </%once> @@ -26,7 +28,8 @@ my %right = ( 'cancel' => 'Cancel customer package immediately', #untaint method my $method = $cgi->param('method'); -$method =~ /^(cancel|expire|suspend|adjourn|resume)$/ or die "Illegal method"; +$method =~ /^(cancel|expire|suspend|adjourn|resume|uncancel)$/ + or die "Illegal method"; $method = $1; my $past_method = $past{$method}; @@ -39,7 +42,7 @@ $pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum"; $pkgnum = $1; my $date = time; -if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){ +if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') { #untaint date $date = $cgi->param('date'); #huh? parse_datetime($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date"; @@ -49,17 +52,22 @@ if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume'){ $method = 'unsuspend' if $method eq 'resume'; } -my $resume_date; +my $resume_date = ''; +my $options = ''; if ( $method eq 'suspend' ) { #or 'adjourn' $resume_date = parse_datetime($cgi->param('resume_date')) if $cgi->param('resume_date'); + + $options = { map { $_ => scalar($cgi->param($_)) } + qw( suspend_bill no_suspend_bill ) + }; } my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); #untaint reasonnum my $reasonnum = $cgi->param('reasonnum'); -if ( $method ne 'unsuspend' ) { #i.e. 'resume' +if ( $method !~ /^(unsuspend|uncancel)$/ ) { $reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum"; $reasonnum = $1; @@ -71,9 +79,22 @@ if ( $method ne 'unsuspend' ) { #i.e. 'resume' } } +#for uncancel +my $last_bill = + $cgi->param('last_bill') ? parse_datetime($cgi->param('last_bill')) : ''; +my $bill = + $cgi->param('bill') ? parse_datetime($cgi->param('bill')) : ''; + +my $svc_fatal = ( $cgi->param('svc_not_fatal') ne 'Y' ); + my $error = $cust_pkg->$method( 'reason' => $reasonnum, 'date' => $date, - 'resume_date' => $resume_date ); + 'resume_date' => $resume_date, + 'last_bill' => $last_bill, + 'bill' => $bill, + 'svc_fatal' => $svc_fatal, + 'options' => $options, + ); if ($error) { $cgi->param('error', $error); diff --git a/httemplate/misc/process/void-cust_bill.html b/httemplate/misc/process/void-cust_bill.html new file mode 100755 index 000000000..899901a50 --- /dev/null +++ b/httemplate/misc/process/void-cust_bill.html @@ -0,0 +1,26 @@ +%if ( $error ) { +% $cgi->param('error', $error); +<% $cgi->redirect(popurl(1). "void-cust_bill.html?". $cgi->query_string ) %> +%} else { +<& /elements/header-popup.html, 'Invoice voided' &> +<SCRIPT TYPE="text/javascript"> + window.top.location.reload(); +</SCRIPT> +</BODY></HTML> +%} +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Void invoices'); + +#untaint invnum +$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum"; +my $invnum = $1; + +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); + +my $custnum = $cust_bill->custnum; + +my $error = $cust_bill->void( $cgi->param('reason') ); + +</%init> diff --git a/httemplate/misc/suspend_cust.html b/httemplate/misc/suspend_cust.html new file mode 100644 index 000000000..b41f36f8c --- /dev/null +++ b/httemplate/misc/suspend_cust.html @@ -0,0 +1,79 @@ +<& /elements/header-popup.html, mt('Suspend customer') &> + +<& /elements/error.html &> + +<FORM NAME="cust_suspend_popup" ACTION="<% popurl(1) %>cust_main-suspend.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> + + <P ALIGN="center"><B><% mt('Suspend this customer?') |h %></B> + +<TABLE BORDER="0" CELLSPACING="2" +STYLE="margin-left:auto; margin-right:auto"> +<TR> + <TD ALIGN="right"> + <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED /> + </TD> + <TD ALIGN="left"><% mt('Suspend now') |h %></TD> +</TR> +<TR> + <TD ALIGN="right"> + <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" /> + </TD> + <TD ALIGN="left"><% mt('Suspend on date: ') |h %> + <& /elements/input-date-field.html, { + 'name' => 'adjourn', + 'value' => time, + } &> + </TD> +</TR> +</TABLE> +<SCRIPT type="text/javascript"> +function toggle(val) { + document.getElementById("adjourn_text").disabled = !val; + document.getElementById("adjourn_button").style.visibility = + val ? 'visible' : 'hidden'; +} +toggle(false); +</SCRIPT> + +<TABLE BGCOLOR="#cccccc", BORDER="0" CELLSPACING="2" +STYLE="margin-left:auto; margin-right:auto"> +<& /elements/tr-select-reason.html, + 'field' => 'reasonnum', + 'reason_class' => 'C', + 'cgi' => $cgi, + 'control_button' => "document.getElementById('confirm_suspend_cust_button')", +&> + +</TABLE> + +<BR> +<P ALIGN="CENTER"> +<INPUT TYPE="submit" NAME="submit" ID="confirm_suspend_cust_button" VALUE="<% mt('Suspend customer') |h %>" DISABLED> + +<INPUT TYPE="BUTTON" VALUE="<% mt("Don't suspend") |h %>" onClick="parent.cClick();"> + +</FORM> +</BODY> +</HTML> + +<%init> + +#false laziness w/cancel_cust.html + +$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum'; +my $custnum = $1; + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" unless $curuser->access_right('Suspend customer'); + +my $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +} ); +die "No customer # $custnum" unless $cust_main; + +</%init> + diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html index 672fad8d6..e4392825c 100755 --- a/httemplate/misc/timeworked.html +++ b/httemplate/misc/timeworked.html @@ -99,8 +99,6 @@ my(%ticketmap, %ticket, %customers); my $title = 'Assign Time Worked'; tie %ticketmap, 'Tie::IxHash'; -RT::Init(); - my $CurrentUser = RT::CurrentUser->new(); $CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username); diff --git a/httemplate/misc/unsuspend_cust.html b/httemplate/misc/unsuspend_cust.html new file mode 100644 index 000000000..600eb268a --- /dev/null +++ b/httemplate/misc/unsuspend_cust.html @@ -0,0 +1,68 @@ +<& /elements/header-popup.html, mt('Unsuspend customer') &> + +<& /elements/error.html &> + +<FORM NAME="cust_unsuspend_popup" ACTION="<% popurl(1) %>cust_main-unsuspend.cgi" METHOD=POST> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> + + <P ALIGN="center"><B><% mt('Unsuspend this customer?') |h %></B> + +<TABLE BORDER="0" CELLSPACING="2" +STYLE="margin-left:auto; margin-right:auto"> +<TR> + <TD ALIGN="right"> + <INPUT TYPE="radio" NAME="now_or_later" VALUE="0" onclick="toggle(false)" CHECKED /> + </TD> + <TD ALIGN="left"><% mt('Unsuspend now') |h %></TD> +</TR> +<TR> + <TD ALIGN="right"> + <INPUT TYPE="radio" NAME="now_or_later" VALUE="1" onclick="toggle(true)" /> + </TD> + <TD ALIGN="left"><% mt('Unsuspend on date: ') |h %> + <& /elements/input-date-field.html, { + 'name' => 'resume', + 'value' => time, + } &> + </TD> +</TR> +</TABLE> +<SCRIPT type="text/javascript"> +function toggle(val) { + document.getElementById("resume_text").disabled = !val; + document.getElementById("resume_button").style.visibility = + val ? 'visible' : 'hidden'; +} +toggle(false); +</SCRIPT> + +<BR> +<P ALIGN="CENTER"> +<INPUT TYPE="submit" NAME="submit" ID="confirm_unsuspend_cust_button" VALUE="<% mt('Unsuspend customer') |h %>"> + +<INPUT TYPE="BUTTON" VALUE="<% mt("Don't unsuspend") |h %>" onClick="parent.cClick();"> + +</FORM> +</BODY> +</HTML> + +<%init> + +#false laziness w/cancel_cust.html + +$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum'; +my $custnum = $1; + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" unless $curuser->access_right('Unsuspend customer'); + +my $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +} ); +die "No customer # $custnum" unless $cust_main; + +</%init> + diff --git a/httemplate/misc/unvoid-cust_bill_void.html b/httemplate/misc/unvoid-cust_bill_void.html new file mode 100755 index 000000000..f61416549 --- /dev/null +++ b/httemplate/misc/unvoid-cust_bill_void.html @@ -0,0 +1,25 @@ +%if ( $error ) { +% errorpage($error); +%} else { +% my $show = $curuser->default_customer_view =~ /^(jumbo|payment_history)$/ +% ? '' +% : ';show=payment_history'; +<% $cgi->redirect($p. "view/cust_main.cgi?custnum=$custnum$show" ) %> +%} +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Unvoid invoices'); + +#untaint invnum +$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum"; +my $invnum = $1; + +my $cust_bill_void = qsearchs('cust_bill_void', { 'invnum' => $invnum } ); +my $custnum = $cust_bill_void->custnum; + +my $error = $cust_bill_void->unvoid; + +</%init> diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi index 91fe1c223..4726ee576 100755 --- a/httemplate/misc/unvoid-cust_pay_void.cgi +++ b/httemplate/misc/unvoid-cust_pay_void.cgi @@ -6,7 +6,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Unvoid'); + unless $FS::CurrentUser::CurrentUser->access_right('Unvoid payments'); #untaint paynum my($query) = $cgi->keywords; diff --git a/httemplate/misc/void-cust_bill.html b/httemplate/misc/void-cust_bill.html new file mode 100644 index 000000000..1608fd051 --- /dev/null +++ b/httemplate/misc/void-cust_bill.html @@ -0,0 +1,45 @@ +<& /elements/header-popup.html, mt('Void invoice') &> + +<% include('/elements/error.html') %> + +<% emt('Are you sure you want to void this invoice?') %> +<BR><BR> + +<% emt("Invoice #[_1] ([_2])",$cust_bill->display_invnum, $money_char. $cust_bill->owed) %> +<BR><BR> + +<FORM METHOD="POST" ACTION="process/void-cust_bill.html"> +<INPUT TYPE="hidden" NAME="invnum" VALUE="<% $invnum %>"> + +<% ntable("#cccccc", 2) %> +<TR> + <TD ALIGN="right">Reason</TD> + <TD><INPUT TYPE="text" NAME="reason" VALUE="<% $cgi->param('reason') |h %>"></TD> +</TR> + +</TABLE> + +<BR> +<CENTER> +<BUTTON TYPE="submit">Yes, void invoice</BUTTON> \ +<BUTTON TYPE="button" onClick="parent.cClick();">No, do not void invoice</BUTTON> +</CENTER> + +</FORM> +</BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Void invoices'); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +#untaint invnum +$cgi->param('invnum') =~ /^(\d+)$/ || die "Illegal invnum"; +my $invnum = $1; + +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); + +</%init> diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi index 7b484e93e..31b7a6201 100755 --- a/httemplate/misc/void-cust_pay.cgi +++ b/httemplate/misc/void-cust_pay.cgi @@ -12,7 +12,7 @@ my $paynum = $1; my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}); -my $right = 'Regular void'; +my $right = 'Void payments'; $right = 'Credit card void' if $cust_pay->payby eq 'CARD'; $right = 'Echeck void' if $cust_pay->payby eq 'CHEK'; diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html new file mode 100644 index 000000000..46f15d1ab --- /dev/null +++ b/httemplate/misc/xmlhttp-cust_bill-search.html @@ -0,0 +1,18 @@ +<% encode_json(\@return) %> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die 'access denied' unless $curuser->access_right('View invoices'); +my @return; +if ( $cgi->param('sub') eq 'custnum_search_open' ) { + my $custnum = $cgi->param('arg'); + #warn "searching invoices for $custnum\n"; + my $cust_main = FS::cust_main->by_key($custnum); + @return = map { + +{ $_->hash, + 'owed' => $_->owed } + } $cust_main->open_cust_bill + if $curuser->agentnums_href->{ $cust_main->agentnum }; +} + +</%init> diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi index 436501e8b..acf7e70e2 100644 --- a/httemplate/misc/xmlhttp-cust_main-search.cgi +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -1,18 +1,26 @@ % if ( $sub eq 'custnum_search' ) { % my $custnum = $cgi->param('arg'); % my $return = []; -% if ( $custnum =~ /^(\d+)$/ ) { -% $return = findbycustnum($1,0); -% $return = findbycustnum($1,1) if(!scalar(@$return)); +% if ( $custnum =~ /^(\d+)$/ ) { #should also handle +% # cust_main-agent_custid-format') eq 'ww?d+' +% $return = findbycustnum_or_agent_custid($1); % } <% objToJson($return) %> % } elsif ( $sub eq 'smart_search' ) { % % my $string = $cgi->param('arg'); % my @cust_main = smart_search( 'search' => $string, -% 'no_fuzzy_on_exact' => 1, #pref? +% 'no_fuzzy_on_exact' => ! $FS::CurrentUser::CurrentUser->option('enable_fuzzy_on_exact'), % ); -% my $return = [ map [ $_->custnum, $_->name, $_->balance, $_->ucfirst_status, $_->statuscolor ], @cust_main ]; +% my $return = [ map [ $_->custnum, +% $_->name, +% $_->balance, +% $_->ucfirst_status, +% $_->statuscolor, +% scalar($_->open_cust_bill) +% ], +% @cust_main +% ]; % <% objToJson($return) %> % } elsif ( $sub eq 'invnum_search' ) { @@ -20,7 +28,7 @@ % my $string = $cgi->param('arg'); % if ( $string =~ /^(\d+)$/ ) { % my $inv = qsearchs('cust_bill', { 'invnum' => $1 }); -% my $return = $inv ? findbycustnum($inv->custnum,0) : []; +% my $return = $inv ? findbycustnum($inv->custnum) : []; <% objToJson($return) %> % } else { #return nothing [] @@ -43,22 +51,58 @@ % } <%init> -my $conf = new FS::Conf; - my $sub = $cgi->param('sub'); -sub findbycustnum{ - my $custnum = shift; - my $agent = shift; - my $hashref = { 'custnum' => $custnum }; - $hashref = { 'agent_custid' => $custnum } if $agent; - my $c = qsearchs({ - 'table' => 'cust_main', - 'hashref' => $hashref, - 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, - }); - return [ $c->custnum, $c->name, $c->balance, $c->ucfirst_status, $c->statuscolor ] - if $c; - []; +sub findbycustnum { + + my $c = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => shift }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }) or return []; + + [ $c->custnum, + $c->name, + $c->balance, + $c->ucfirst_status, + $c->statuscolor, + scalar($c->open_cust_bill) + ]; +} + +sub findbycustnum_or_agent_custid { + my $num = shift; + + my @or = ( 'agent_custid = ?' ); + my @param = ( $num ); + + if ( $num =~ /^\d+$/ && $num <= 2147483647 ) { #need a bigint custnum? wow + my $conf = new FS::Conf; + if ( $conf->exists('cust_main-default_agent_custid') ) { + push @or, "( agent_custid IS NULL AND custnum = $num )"; + } else { + push @or, "custnum = $num"; + } + } + + my $extra_sql = ' WHERE '. $FS::CurrentUser::CurrentUser->agentnums_sql. + ' AND ( '. join(' OR ', @or). ' )'; + + [ map [ $_->custnum, + $_->name, + $_->balance, + $_->ucfirst_status, + $_->statuscolor, + scalar($_->open_cust_bill), + ], + + qsearch({ + 'table' => 'cust_main', + 'hashref' => {}, + 'extra_sql' => $extra_sql, + 'extra_param' => \@param, + }) + ]; } + </%init> diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index 974b96dc2..c4fef0311 100644 --- a/httemplate/pref/pref-process.html +++ b/httemplate/pref/pref-process.html @@ -48,8 +48,10 @@ unless ( $error ) { # if ($access_user) { my %param = $access_user->options; #XXX autogen - my @paramlist = qw( locale menu_position default_customer_view mobile_menu - disable_html_editor + my @paramlist = qw( locale menu_position default_customer_view + spreadsheet_format mobile_menu + enable_fuzzy_on_exact + disable_html_editor disable_enter_submit_onetimecharge email_address snom-ip snom-username snom-password vonage-fromnumber vonage-username vonage-password diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html index 8fd1eaa73..575b8045b 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -32,7 +32,7 @@ Interface <TR> <TH ALIGN="right">Locale: </TH> - <TD> + <TD COLSPAN=2> <SELECT NAME="locale"> % foreach my $locale ( FS::Locales->locales ) { % my %info = FS::Locales->locale_info($locale); @@ -75,7 +75,29 @@ Interface </SELECT> </TD> </TR> - + + <TR> + <TH ALIGN="right">Spreadsheet download format: </TH> + <TD COLSPAN=2> + <SELECT NAME="spreadsheet_format"> +% my $xls = $curuser->option('spreadsheet_format') eq 'XLS'; +% my $xlsx = $curuser->option('spreadsheet_format') eq 'XLSX'; + <OPTION VALUE=""></OPTION> + <OPTION VALUE="XLS"<% $xls ? 'SELECTED' : '' %>>XLS (Excel 97/2000/XP) + </OPTION> + <OPTION VALUE="XLSX"<% $xlsx ? 'SELECTED' : ''%>>XLSX (Excel 2007+) + </OPTION> + </SELECT> + </TD> + </TR> + + <TR> + <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching even when an exact match is found: </TH> + <TD ALIGN="left" COLSPAN=2> + <INPUT TYPE="checkbox" NAME="enable_fuzzy_on_exact" VALUE="1" <% $curuser->option('enable_fuzzy_on_exact') ? 'CHECKED' : '' %>> + </TD> + </TR> + <TR> <TH ALIGN="right" COLSPAN=1>Disable HTML editor for customer notes: </TH> <TD ALIGN="left" COLSPAN=2> @@ -83,6 +105,13 @@ Interface </TD> </TR> + <TR> + <TH ALIGN="right" COLSPAN=1>Disable submission on [Enter] key - one-time charges: </TH> + <TD ALIGN="left" COLSPAN=2> + <INPUT TYPE="checkbox" NAME="disable_enter_submit_onetimecharge" VALUE="1" <% $curuser->option('disable_enter_submit_onetimecharge') ? 'CHECKED' : '' %>> + </TD> + </TR> + </TABLE> <BR> diff --git a/httemplate/search/477.html b/httemplate/search/477.html index 250e71811..6f5fcdf3b 100755 --- a/httemplate/search/477.html +++ b/httemplate/search/477.html @@ -1,33 +1,24 @@ -% unless ( $type eq 'xml' ) { -<% include( '/elements/header.html', 'FCC Form 477 Results') %> -%}else{ +% if ( $type eq 'xml' ) { <?xml version="1.0" encoding="ISO-8859-1"?> <Form_477_submission xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://specialreports.fcc.gov/wcb/Form477/XMLSchema-instance/form_477_upload_Schema.xsd" > -%} -% if ( $type eq 'html' || $type eq 'html-print' ) { +% } else { #html +<& /elements/header.html, "FCC Form 477 Results - $state" &> <TABLE WIDTH="100%"> - <TR><TD></TD> -%}elsif ( $type eq 'xml' ) { -%} -% unless ( $type eq 'html-print' || $type eq 'xml' ) { + <TR> + <TD></TD> + <TD ALIGN="right" CLASS="noprint"> + Download full results<BR> +% $cgi->param('_type', 'xml'); + as <A HREF="<% $cgi->self_url %>">XML file</A><BR> - <TD ALIGN="right"> +% $cgi->param('_type', 'html-print'); + as <A HREF="<% $cgi->self_url %>">printable copy</A> - Download full results<BR> -% $cgi->param('_type', 'xml'); - as <A HREF="<% $cgi->self_url %>">XML file</A><BR> - -% $cgi->param('_type', 'html-print'); - as <A HREF="<% $cgi->self_url %>">printable copy</A> - - </TD> -% $cgi->param('_type', $type ); -% } -% if ( $type eq 'html' || $type eq 'html-print' ) { + </TD> +% $cgi->param('_type', $type ); </TR> </TABLE> -%}elsif ( $type eq 'xml' ) { -%} +% } #html % foreach my $part ( @parts ) { % if ( $part{$part} ) { % @@ -47,8 +38,8 @@ % if ( $type eq 'xml' ) { <<% 'Part_IA_'. chr(65 + $tech) %>> % } -<% include( "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url ) %> -<% include( "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url ) %> +<& "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url &> +<& "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url &> % if ( $type eq 'xml' ) { </<% 'Part_IA_'. chr(65 + $tech) %>> % } @@ -58,7 +49,7 @@ <<% 'Part_'. $part %>> % } % my $url = &{$url_mangler}($part); -<% include( "477part${part}.html", 'url' => $url ) %> +<& "477part${part}.html", 'url' => $url &> % if ( $type eq 'xml' ) { </<% 'Part_'. $part %>> % } @@ -66,11 +57,11 @@ % } % } % -% if ( $type eq 'html' || $type eq 'html-print' ) { -<% include( '/elements/footer.html') %> -%}elsif ( $type eq 'xml' ) { +% if ( $type eq 'xml' ) { </Form_477_submission> -%} +% } else { +<& /elements/footer.html &> +% } <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -78,6 +69,9 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('List packages'); +my $state = uc($cgi->param('state')); +$state =~ /^[A-Z]{2}$/ or die "illegal state: $state"; + my %part = map { $_ => 1 } grep { /^\w+$/ } $cgi->param('part'); my $type = $cgi->param('_type') || 'html'; my $xlsname = '477report'; diff --git a/httemplate/search/477partIA_detail.html b/httemplate/search/477partIA_detail.html index 2eca1072b..66f3a8651 100755 --- a/httemplate/search/477partIA_detail.html +++ b/httemplate/search/477partIA_detail.html @@ -23,9 +23,10 @@ die "access denied" my %opt = @_; my %search_hash = (); -for ( qw(agentnum magic) ) { +for ( qw(agentnum magic state) ) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } +$search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ]; diff --git a/httemplate/search/477partIA_summary.html b/httemplate/search/477partIA_summary.html index ecacaefad..f5c2bc251 100755 --- a/httemplate/search/477partIA_summary.html +++ b/httemplate/search/477partIA_summary.html @@ -40,9 +40,10 @@ die "access denied" my %opt = @_; my %search_hash = (); -for ( qw(agentnum magic) ) { +for ( qw(agentnum magic state) ) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } +$search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ]; my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option') diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html index 9b363ad5e..d2cc8c3e9 100755 --- a/httemplate/search/477partIIA.html +++ b/httemplate/search/477partIIA.html @@ -22,9 +22,10 @@ die "access denied" my $html_init = '<H2>Part IIA</H2>'; my %search_hash = (); -for ( qw(agentnum magic) ) { +for ( qw(agentnum magic state) ) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } +$search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ]; my @row_option = grep { /^\d+$/ } $cgi->param('part2a_row_option') diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html index 94aa818fb..c58310d36 100755 --- a/httemplate/search/477partIIB.html +++ b/httemplate/search/477partIIB.html @@ -1,17 +1,44 @@ -<% include( 'elements/search.html', - 'html_init' => $html_init, - 'name' => 'lines', - 'query' => $query, - 'count_query' => 'SELECT 11', - 'really_disable_download' => 1, - 'disable_download' => 1, - 'nohtmlheader' => 1, - 'disable_total' => 1, - 'header' => [ @headers ], - 'xml_elements' => [ @xml_elements ], - 'fields' => [ @fields ], - ) -%> +% if ( $cgi->param('_type') eq 'xml' ) { +% my @cols = qw(a b c); +% for ( my $row = 0; $row < scalar(@rows); $row++ ) { +% for my $col (0..2) { +% if ( exists($data[$col][$row]) ) { +<PartII_<% $row %><% $cols[$col] %>> +% } +</PartII_<% $row %><% $cols[$col] %>> +% } #for $col +% } #for $row +% } else { # HTML mode +% # fake up the search-html.html header +<H2>Part IIB</H2> +<TABLE> + <TR><TD VALIGN="bottom"><BR></TD></TR> + <TR><TD COLSPAN=2> + <TABLE CLASS="grid" CELLSPACING=0 STYLE="border: 1px solid #cccccc;" BGCOLOR="#cccccc"> + <TR> +% foreach (@headers) { + <TH class="grid"><% $_ %></TH> +% } + </TR> +% my @bgcolor = ('eeeeee','ffffff'); +% my $row = 0; +% foreach my $rowhead (@rows) { + <TR> + <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"><% $rowhead %></TD> +% for my $col (0..2) { + <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"> +% if ( exists($data[$col][$row]) ) { + <% $data[$col][$row] %> +% } + </TD> +% } # for $col + </TR> +% $row++; +% } #for $rowhead + </TABLE> + </TD></TR> +</TABLE> +% } #XML/HTML <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -19,67 +46,89 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('List packages'); -my $html_init = '<H2>Part IIB</H2>'; my %search_hash = (); - -for ( qw(agentnum magic) ) { - $search_hash{$_} = $cgi->param($_) if $cgi->param($_); -} -$search_hash{'classnum'} = [ $cgi->param('classnum') ]; - -my @row_option = grep { /^\d+$/ } $cgi->param('part2b_row_option') - if $cgi->param('part2b_row_option'); - -# fudge in 2nd row -unshift @row_option, $row_option[0]; - -my $query = 'SELECT '. join(' UNION SELECT ', 1..8); - -my $total_count = 0; -my $column_value = sub { - my $row = shift; - - my @report_option = ( $row_option[$row - 1] || '' ); - my $sql_query = FS::cust_pkg->search( - { %search_hash, 'report_option' => join(',', @report_option) } - ); - - my $count_sql = delete($sql_query->{'count_query'}); - if ( $row == 2 ) { - $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END ELSE 0 END, 0) ) FROM/ - or die "couldn't parse count_sql"; - } else { - $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END, 0)) FROM/ - or die "couldn't parse count_sql"; - } - - my $count_sth = dbh->prepare($count_sql) - or die "Error preparing $count_sql: ". dbh->errstr; - $count_sth->execute - or die "Error executing $count_sql: ". $count_sth->errstr; - my $count_arrayref = $count_sth->fetchrow_arrayref; - my $count = $count_arrayref->[0]; +$search_hash{'agentnum'} = $cgi->param('agentnum'); +$search_hash{'state'} = $cgi->param('state'); +$search_hash{'classnum'} = [ $cgi->param('classnum') ]; +$search_hash{'status'} = 'active'; - $total_count = $count if $row == 1; - $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0) - if $row != 1; +my @row_option; +foreach ($cgi->param('part2b_row_option')) { + push @row_option, (/^\d+$/ ? $_ : undef); +} - return "$count"; +my $is_residential = "AND COALESCE(cust_main.company, '') = ''"; +my $has_report_option = sub { + map { + defined($row_option[$_]) ? + "AND EXISTS( + SELECT 1 FROM part_pkg_option + WHERE part_pkg_option.pkgpart = part_pkg.pkgpart + AND optionname = 'report_option_" . $row_option[$_]."' + AND optionvalue = '1' + )" : 'AND FALSE' + } @_ }; -my @headers = ( - '', - 'without broadband', - 'with broadband', - 'wholesale', +# an arrayref for each column +my @data; +# get the skeleton of the query +my $sql_query = FS::cust_pkg->search(\%search_hash); +my $from_where = $sql_query->{'count_query'}; +$from_where =~ s/^SELECT COUNT\(\*\) //; +# columns 1 and 2 +my $query_ds0 = "SELECT SUM(COALESCE(part_pkg.fcc_ds0s, pkg_class.fcc_ds0s, 0)) + $from_where"; +# column 3 +my $query_custnum = "SELECT COUNT(DISTINCT cust_pkg.custnum) $from_where"; + +my @base_queries = ($query_ds0, $query_ds0, $query_custnum); +my @col_conds = ( + # column 1 + [ + '', + $is_residential, + $has_report_option->(0), # nomadic + ], + # column 2 + [ + '', + $is_residential, + $has_report_option->(0..5), + ], + # column 3 + [ + '' + ] ); -my @xml_elements = ( - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" }, - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" }, - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" }, -); +my $col = 0; +foreach (@col_conds) { + my @col_data; + my $row = 0; + foreach my $cond (@{ $col_conds[$col] }) { + # three parts: the select expression, the VoIP class (column selection), + # and the row selection + my $query = $base_queries[$col] . + " AND part_pkg.fcc_voip_class = '".($col+1)."' + $cond"; + my $count = FS::Record->scalar_sql($query) || 0; + if ( $row == 0 ) { + $col_data[$row] = $count; # the raw count + } else { + if ( $col_data[0] == 0 ) { + $col_data[$row] = ''; # show nothing in this row, then + } else { + $col_data[$row] = sprintf('%.2f', 100 * $count / $col_data[0]) . '%'; + } + } #if $row == 0 + $row++; + } + $data[$col] = \@col_data; + $col++; +} + my @rows = ( 'total number', @@ -92,12 +141,11 @@ my @rows = ( '% other broadband', ); -my @fields = ( - sub { my $row = shift; $rows[$row->[0] - 1]; }, - sub { 0; }, - sub { my $row = shift; &{$column_value}($row->[0]); }, - sub { 0; }, +my @headers = ( + '', + 'without broadband', + 'with broadband', + 'wholesale', ); -shift @fields if $cgi->param('_type') eq 'xml'; </%init> diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html index 55ebc0be2..2fd5119d1 100755 --- a/httemplate/search/477partV.html +++ b/httemplate/search/477partV.html @@ -10,7 +10,7 @@ 'no_field_elements' => 1, 'fields' => [ 'zip' ], 'url' => $opt{url} || '', - 'disable_download' => 1, + 'really_disable_download' => 1, ) %> @@ -27,16 +27,19 @@ my %search_hash = (); my @sql_query = (); my @count_query = (); -for ( qw(agentnum magic) ) { +for ( qw(agentnum magic state) ) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } +$search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ]; $search_hash{report_option} = $cgi->param('partv_report_option') if $cgi->param('partv_report_option'); -my $sql_query = FS::cust_pkg->search( { %search_hash, 'fcc_line' => 1 }); -$sql_query->{select} = 'DISTINCT substr(zip,1,5) as zip'; -$sql_query->{order_by} = 'ORDER BY substr(zip,1,5)'; +my $sql_query = FS::cust_pkg->search( { %search_hash, + 'fcc_line' => 1, + 'select_zip5' => 1, + } + ); my $count_query = delete($sql_query->{'count_query'}); $count_query =~ s/COUNT\(\*\)/count(DISTINCT substr(zip,1,5))/; $count_query =~ s/ORDER BY [.\w]+//; diff --git a/httemplate/search/477partVI_census.html b/httemplate/search/477partVI_census.html index 4d1fb2136..8425c4b48 100755 --- a/httemplate/search/477partVI_census.html +++ b/httemplate/search/477partVI_census.html @@ -23,6 +23,7 @@ 'links' => \@links, 'url' => $opt{url} || '', 'xml_row_element' => 'Datarow', + 'really_disable_download' => 1, ) %> <%init> @@ -80,9 +81,10 @@ push @fields, my %search_hash = (); my @sql_query = (); -for ( qw(agentnum magic) ) { +for ( qw(agentnum magic state) ) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } +$search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ] if grep { $_ eq 'classnum' } $cgi->param; @@ -115,10 +117,10 @@ foreach my $row ( @row_option ) { ); my $extracolumns = "$rowcount AS upload, $columncount AS download, $tech_code as technology_code"; my $percent = "CASE WHEN count(*) > 0 THEN 100-100*cast(count(cust_main.company) as numeric)/cast(count(*) as numeric) ELSE cast(0 as numeric) END AS residential"; - $sql_query->{select} = "count(*) AS quantity, $extracolumns, censustract, $percent"; + $sql_query->{select} = "count(*) AS quantity, $extracolumns, cust_location.censustract, $percent"; $sql_query->{order_by} =~ /^(.*)(ORDER BY pkgnum)(.*)$/s or die "couldn't parse order_by"; - $sql_query->{order_by} = "$1 GROUP BY censustract $3"; + $sql_query->{order_by} = "$1 GROUP BY cust_location.censustract $3"; push @sql_query, $sql_query; } $columncount++; @@ -131,7 +133,8 @@ my $count_query = 'SELECT count(*) FROM ( ('. map { my $addl_from = $_->{addl_from}; my $extra_sql = $_->{extra_sql}; my $order_by = $_->{order_by}; - "SELECT censustract from cust_pkg $addl_from $extra_sql $order_by"; + "SELECT cust_location.censustract from cust_pkg $addl_from + $extra_sql $order_by"; } @sql_query ). ') ) AS foo'; diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 813f9b843..406486a85 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -93,6 +93,10 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { $search{'agentnum'} = $1; } + if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { + $search{'refnum'} = $1; + } + if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { $search{'custnum'} = $1; } diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html index 1fc8ffd78..22e9a6795 100644 --- a/httemplate/search/cust_bill_pay.html +++ b/httemplate/search/cust_bill_pay.html @@ -92,6 +92,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { $title = $agent->agent. " $title"; } +if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @search, "refnum = $1"; + my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } ); + die "unknown refnum $1" unless $part_referral; + $title = $part_referral->referral. " $title"; +} + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); push @search, "cust_bill._date >= $beginning ", "cust_bill._date <= $ending"; diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 94860d3f2..4c0fa4a56 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -3,23 +3,10 @@ 'name' => emt('line items'), 'query' => $query, 'count_query' => $count_query, - 'count_addl' => [ $money_char. '%.2f total', - $unearned ? ( $money_char. '%.2f unearned revenue' ) : (), - ], + 'count_addl' => \@total_desc, 'header' => [ emt('Description'), - ( $unearned - ? ( emt('Unearned'), emt('Owed'), emt('Payment date') ) - : ( emt('Setup charge') ) - ), - ( $use_usage eq 'usage' - ? emt('Usage charge') - : emt('Recurring charge') - ), - ( $unearned - ? ( emt('Charge start'), emt('Charge end') ) - : () - ), + @peritem_desc, emt('Invoice'), emt('Date'), FS::UI::Web::cust_header(), @@ -31,66 +18,21 @@ }, #strikethrough or "N/A ($amount)" or something these when # they're not applicable to pkg_tax search - sub { my $cust_bill_pkg = shift; - if ( $unearned ) { - my $period = - $cust_bill_pkg->edate - $cust_bill_pkg->sdate; - my $elapsed = $unearned - $cust_bill_pkg->sdate; - $elapsed = 0 if $elapsed < 0; - - my $remaining = 1 - $elapsed/$period; - - sprintf($money_char. '%.2f', - $remaining * $cust_bill_pkg->recur ); - - } else { - sprintf($money_char.'%.2f', $cust_bill_pkg->setup ); - } - }, - ( $unearned - ? ( $owed_sub, $payment_date_sub, ) - : () - ), - sub { my $row = shift; - my $value = 0; - if ( $use_usage eq 'recurring' ) { - $value = $row->recur - $row->usage; - } elsif ( $use_usage eq 'usage' ) { - $value = $row->usage; - } else { - $value = $row->recur; - } - sprintf($money_char.'%.2f', $value ); - }, - ( $unearned - ? ( sub { time2str('%b %d %Y', shift->sdate ) }, - sub { time2str('%b %d %Y', shift->edate ) }, - ) - : () - ), + @peritem_sub, 'invnum', sub { time2str('%b %d %Y', shift->_date ) }, \&FS::UI::Web::cust_fields, ], 'sort_fields' => [ '', - 'setup', #broken in $unearned case i guess - ( $unearned ? ('', '') : () ), - ( $use_usage eq 'recurring' ? 'recur - usage' : - $use_usage eq 'usage' ? 'usage' - : 'recur' - ), - ( $unearned ? ('sdate', 'edate') : () ), + @peritem, 'invnum', '_date', ], 'links' => [ #'', '', - '', - ( $unearned ? ( '', '' ) : () ), - '', - ( $unearned ? ( '', '' ) : () ), + @peritem_null, $ilink, $ilink, ( map { $_ ne 'Cust. Status' ? $clink : '' } @@ -98,19 +40,14 @@ ), ], #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(), - 'align' => 'lr'. - ( $unearned ? 'rc' : '' ). - 'r'. - ( $unearned ? 'cc' : '' ). + 'align' => 'l'. + $peritem_align. 'rc'. FS::UI::Web::cust_aligns(), 'color' => [ #'', '', - '', - ( $unearned ? ( '', '' ) : () ), - '', - ( $unearned ? ( '', '' ) : () ), + @peritem_null, '', '', FS::UI::Web::cust_colors(), @@ -118,32 +55,124 @@ 'style' => [ #'', '', - '', - ( $unearned ? ( '', '' ) : () ), - '', - ( $unearned ? ( '', '' ) : () ), + @peritem_null, '', '', FS::UI::Web::cust_styles(), ], &> -<%init> +<%doc> + +Output parameters: +- distribute: Boolean. If true, recurring fees will be "prorated" for the + portion of the package date range (sdate-edate) that falls within the date + range of the report. Line items will be limited to those for which this + portion is > 0. This disables filtering on invoice date. + +- use_usage: Separate usage (cust_bill_pkg_detail records) from + recurring charges. If set to "usage", will show usage instead of + recurring charges. If set to "recurring", will deduct usage and only + show the flat rate charge. If not passed, the "recurring charge" column + will include usage charges also. + +Filtering parameters: +- begin, end: Date range. Applies to invoice date, not necessarily package + date range. But see "distribute". + +- status: Customer status (active, suspended, etc.). This will filter on + _current_ customer status, not status at the time the invoice was generated. + +- agentnum: Filter on customer agent. + +- refnum: Filter on customer reference source. + +- classnum: Filter on package class. + +- use_override: Apply "classnum" and "taxclass" filtering based on the + override (bundle) pkgpart, rather than always using the true pkgpart. + +- nottax: Limit to items that are not taxes (pkgnum > 0). + +- istax: Limit to items that are taxes (pkgnum == 0). + +- taxnum: Limit to items whose tax definition matches this taxnum. + With "nottax" that means items that are subject to that tax; + with "istax" it's the tax charges themselves. Can be specified + more than once to include multiple taxes. + +- country, state, county, city: Limit to items whose tax location + matches these fields. If "nottax" it's the tax location of the package; + if "istax" the location of the tax. + +- taxname, taxnameNULL: With "nottax", limit to items whose tax location + matches a tax with this name. With "istax", limit to items that have + this tax name. taxnameNULL is equivalent to "taxname = '' OR taxname + = 'Tax'". + +- out: With "nottax", limit to items that don't match any tax definition. + With "istax", find tax items that are unlinked to their tax definitions. + Current Freeside (> July 2012) always creates tax links, but unlinked + items may result from an incomplete upgrade of legacy data. + +- locationtaxid: With "nottax", limit to packages matching this + tax_rate_location ID; with "tax", limit to taxes generated from that + location. + +- taxclass: Filter on package taxclass. + +- taxclassNULL: With "nottax", limit to items that would be subject to the + tax with taxclass = NULL. This doesn't necessarily mean part_pkg.taxclass + is NULL; it also includes taxclasses that don't have a tax in this region. -#LOTS of false laziness below w/cust_credit_bill_pkg.cgi +- itemdesc: Limit to line items with this description. Note that non-tax + packages usually have a description of NULL. (Deprecated.) + +- report_group: Can contain '=' or '!=' followed by a string to limit to + line items where itemdesc starts with, or doesn't start with, the string. + +- cust_tax: Limit to customers who are tax-exempt. If "taxname" is also + specified, limit to customers who are also specifically exempt from that + tax. + +- pkg_tax: Limit to packages that are tax-exempt, and only include the + exempt portion (setup, recurring, or both) when calculating totals. + +- taxable: Limit to packages that are subject to tax, i.e. where a + cust_bill_pkg_tax_location record exists. + +- credit: Limit to line items that received a credit application. The + amount of the credit will also be shown. + +</%doc> +<%init> die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; -my $unearned = ''; +my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' ); +my @total = ( 'COUNT(*)', 'SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)'); +my @total_desc = ( '%d line items', $money_char.'%.2f total' ); # sprintf strings +my @peritem = ( 'setup', 'recur' ); +my @peritem_desc = ( 'Setup charge', 'Recurring charge' ); +my ($join_cust, $join_pkg ) = ('', ''); +my $use_usage; + +# valid in both the tax and non-tax cases +$join_cust = + " LEFT JOIN cust_bill USING (invnum) + LEFT JOIN cust_main USING (custnum) + "; -#here is the agent virtualization +#agent virtualization my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' ); my @where = ( $agentnums_sql ); +# date range my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); if ( $cgi->param('distribute') == 1 ) { @@ -152,457 +181,407 @@ if ( $cgi->param('distribute') == 1 ) { ; } else { - push @where, "_date >= $beginning", - "_date <= $ending"; + push @where, "cust_bill._date >= $beginning", + "cust_bill._date <= $ending"; } +# status +if ( $cgi->param('status') =~ /^([a-z]+)$/ ) { + push @where, FS::cust_main->cust_status_sql . " = '$1'"; +} + +# agentnum if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { push @where, "cust_main.agentnum = $1"; } -#classnum -# not specified: all classes -# 0: empty class -# N: classnum -my $use_override = $cgi->param('use_override'); -if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { - my $comparison = ''; - if ( $1 == 0 ) { - $comparison = "IS NULL"; - } else { - $comparison = "= $1"; - } - - if ( $use_override ) { - push @where, "( - part_pkg.classnum $comparison AND pkgpart_override IS NULL OR - override.classnum $comparison AND pkgpart_override IS NOT NULL - )"; - } else { - push @where, "part_pkg.classnum $comparison"; - } +# refnum +if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.refnum = $1"; } -if ( $cgi->param('taxclass') - && ! $cgi->param('istax') #no part_pkg.taxclass in this case - #(should we save a taxclass or a link to taxnum - # in cust_bill_pkg or something like - # cust_bill_pkg_tax_location?) - ) -{ - - #override taxclass when use_override is specified? probably - #if ( $use_override ) { - # - # push @where, - # ' ( '. join(' OR ', - # map { - # ' ( part_pkg.taxclass = '. dbh->quote($_). - # ' AND pkgpart_override IS NULL '. - # ' OR '. - # ' override.taxclass = '. dbh->quote($_). - # ' AND pkgpart_override IS NOT NULL '. - # ' ) ' - # } - # $cgi->param('taxclass') - # ). - # ' ) '; - # - #} else { - - push @where, ' part_pkg.taxclass IN ( '. - join(', ', map dbh->quote($_), $cgi->param('taxclass') ). - ' ) '; - - #} - -} +# the non-tax case +if ( $cgi->param('nottax') ) { -my @loc_param = qw( city county state country ); + push @where, 'cust_bill_pkg.pkgnum > 0'; -if ( $cgi->param('out') ) { + # then we want the package and its definition + $join_pkg = +' LEFT JOIN cust_pkg USING (pkgnum) + LEFT JOIN part_pkg USING (pkgpart)'; - my ( $loc_sql, @param ) = FS::cust_pkg->location_sql( 'ornull' => 1 ); - while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution - $loc_sql =~ s/\?/'cust_main_county.'.shift(@param)/e; + my $part_pkg = 'part_pkg'; + if ( $cgi->param('use_override') ) { + # still need the real part_pkg for tax applicability, + # so alias this one + $join_pkg .= " LEFT JOIN part_pkg AS override ON ( + COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart + )"; + $part_pkg = 'override'; } + push @select, 'part_pkg.pkg'; # or should this use override? - $loc_sql =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g - if $cgi->param('istax'); - - push @where, " - 0 = ( - SELECT COUNT(*) FROM cust_main_county - WHERE cust_main_county.tax > 0 - AND $loc_sql - ) - "; - - #not linked to by anything, but useful for debugging "out of taxable region" - if ( grep $cgi->param($_), @loc_param ) { - - my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param; - - my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; - while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution - $loc_sql =~ s/\?/$ph{shift(@param)}/e; - } - - push @where, $loc_sql; + my @tax_where; # will go into a subquery + my @exempt_where; # will also go into a subquery + # classnum (of override pkgpart if applicable) + # not specified: all classes + # 0: empty class + # N: classnum + if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + push @where, "COALESCE($part_pkg.classnum, 0) = $1"; } -} elsif ( $cgi->param('country') ) { - - my @counties = $cgi->param('county'); - - if ( scalar(@counties) > 1 ) { - - #hacky, could be more efficient. care if it is ever used for more than the - # tax-report_groups filtering kludge - - my $locs_sql = - ' ( '. join(' OR ', map { - - my %ph = ( 'county' => dbh->quote($_), - map { $_ => dbh->quote( $cgi->param($_) ) } - qw( city state country ) - ); + # taxclass + if ( $cgi->param('taxclassNULL') ) { + # a little different from 'taxclass' in that it applies to the + # effective taxclass, not the real one + push @tax_where, 'cust_main_county.taxclass IS NULL' + } elsif ( $cgi->param('taxclass') ) { + push @tax_where, "$part_pkg.taxclass IN (" . + join(', ', map {dbh->quote($_)} $cgi->param('taxclass') ). + ')'; + } - my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; - while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution - $loc_sql =~ s/\?/$ph{shift(@param)}/e; - } + if ( $cgi->param('exempt_cust') eq 'Y' ) { + # tax-exempt customers + push @exempt_where, "(exempt_cust = 'Y' OR exempt_cust_taxname = 'Y')"; - $loc_sql; + } elsif ( $cgi->param('exempt_pkg') eq 'Y' ) { # non-taxable package + # non-taxable package charges + push @exempt_where, "(exempt_setup = 'Y' OR exempt_recur = 'Y')"; + } + # we don't handle exempt_monthly here + + if ( $cgi->param('taxname') ) { # specific taxname + push @tax_where, 'cust_main_county.taxname = '. + dbh->quote($cgi->param('taxname')); + } elsif ( $cgi->param('taxnameNULL') ) { + push @tax_where, 'cust_main_county.taxname IS NULL OR '. + 'cust_main_county.taxname = \'Tax\''; + } - } @counties + # country:state:county:city:district (may be repeated) + # You can also pass a big list of taxnums but that leads to huge URLs. + # Note that this means "packages whose tax is in this region", not + # "packages in this region". It's meant for links from the tax report. + if ( $cgi->param('region') ) { + my @orwhere; + foreach ( $cgi->param('region') ) { + my %loc; + @loc{qw(country state county city district)} = + split(':', $cgi->param('region')); + my $string = join(' AND ', + map { + if ( $loc{$_} ) { + "$_ = ".dbh->quote($loc{$_}); + } else { + "$_ IS NULL"; + } + } keys(%loc) + ); + push @orwhere, "($string)"; + } + push @tax_where, '(' . join(' OR ', @orwhere) . ')' if @orwhere; + } - ). ' ) '; + # specific taxnums + if ( $cgi->param('taxnum') ) { + my $taxnum_in = join(',', + grep /^\d+$/, $cgi->param('taxnum') + ); + push @tax_where, "cust_main_county.taxnum IN ($taxnum_in)" + if $taxnum_in; + } - push @where, $locs_sql; + # If we're showing exempt items, we need to find those with + # cust_tax_exempt_pkg records matching the selected taxes. + # If we're showing taxable items, we need to find those with + # cust_bill_pkg_tax_location records. We also need to find the + # exemption records so that we can show the taxable amount. + # If we're showing all items, we need the union of those. + # If we're showing 'out' (items that aren't region/class taxable), + # then we need the set of all items minus the union of those. - } else { + my $exempt_sub; - my %ph = map { $_ => dbh->quote( scalar($cgi->param($_)) ) } @loc_param; + if ( @exempt_where or @tax_where + or $cgi->param('taxable') or $cgi->param('out') ) + { + # process exemption restrictions, including @tax_where + my $exempt_sub = 'SELECT SUM(amount) as exempt_amount, billpkgnum + FROM cust_tax_exempt_pkg JOIN cust_main_county USING (taxnum)'; - my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; - while ( $loc_sql =~ /\?/ ) { #easier to do our own substitution - $loc_sql =~ s/\?/$ph{shift(@param)}/e; - } + $exempt_sub .= ' WHERE '.join(' AND ', @tax_where, @exempt_where) + if (@tax_where or @exempt_where); - push @where, $loc_sql; + $exempt_sub .= ' GROUP BY billpkgnum'; + $join_pkg .= " LEFT JOIN ($exempt_sub) AS item_exempt + USING (billpkgnum)"; } - - if ( $cgi->param('istax') ) { - if ( $cgi->param('taxname') ) { - push @where, 'itemdesc = '. dbh->quote( $cgi->param('taxname') ); - #} elsif ( $cgi->param('taxnameNULL') { - } else { - push @where, "( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; - } - } elsif ( $cgi->param('nottax') ) { - #what can we usefully do with "taxname" ???? look up a class??? - } else { - #warn "neither nottax nor istax parameters specified"; - } - - if ( $cgi->param('taxclassNULL') - && ! $cgi->param('istax') #no part_pkg.taxclass in this case - #(see comment above?) - ) - { - my %hash = ( 'country' => scalar($cgi->param('country')) ); - foreach (qw( state county )) { - $hash{$_} = scalar($cgi->param($_)) if $cgi->param($_); - } - my $cust_main_county = qsearchs('cust_main_county', \%hash); - die "unknown base region for empty taxclass" unless $cust_main_county; - - my $same_sql = $cust_main_county->sql_taxclass_sameregion; - $same_sql =~ s/taxclass/part_pkg.taxclass/g; - push @where, $same_sql if $same_sql; - + + if ( @tax_where or $cgi->param('taxable') or $cgi->param('out') ) { + # process tax restrictions + unshift @tax_where, + 'cust_main_county.tax > 0'; + + my $tax_sub = "SELECT invnum, cust_bill_pkg_tax_location.pkgnum + FROM cust_bill_pkg_tax_location + JOIN cust_bill_pkg AS tax_item USING (billpkgnum) + JOIN cust_main_county USING (taxnum) + WHERE ". join(' AND ', @tax_where). + " GROUP BY invnum, cust_bill_pkg_tax_location.pkgnum"; + + $join_pkg .= " LEFT JOIN ($tax_sub) AS item_tax + ON (item_tax.invnum = cust_bill_pkg.invnum AND + item_tax.pkgnum = cust_bill_pkg.pkgnum)"; } -} elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) { + # now do something with that + if ( @exempt_where ) { - push @where, FS::tax_rate_location->location_sql( - map { $_ => (scalar($cgi->param($_)) || '') } - qw( city county state locationtaxid ) - ); + push @where, 'item_exempt.billpkgnum IS NOT NULL'; + push @select, 'item_exempt.exempt_amount'; + push @peritem, 'exempt_amount'; + push @peritem_desc, 'Exempt'; + push @total, 'SUM(exempt_amount)'; + push @total_desc, "$money_char%.2f tax-exempt"; -} elsif ( $cgi->param('unearned_now') =~ /^(\d+)$/ ) { + } elsif ( $cgi->param('taxable') ) { - $unearned = $1; + my $taxable = 'cust_bill_pkg.setup + cust_bill_pkg.recur '. + '- COALESCE(item_exempt.exempt_amount, 0)'; - push @where, "cust_bill_pkg.sdate < $unearned", - "cust_bill_pkg.edate > $unearned", - "cust_bill_pkg.recur != 0", - "part_pkg.freq != '0'", - "part_pkg.freq != '1'", - "part_pkg.freq NOT LIKE '%h'", - "part_pkg.freq NOT LIKE '%d'", - "part_pkg.freq NOT LIKE '%w'"; + push @where, 'item_tax.invnum IS NOT NULL'; + push @select, "($taxable) AS taxable_amount"; + push @peritem, 'taxable_amount'; + push @peritem_desc, 'Taxable'; + push @total, "SUM($taxable)"; + push @total_desc, "$money_char%.2f taxable"; -} - -if ( $cgi->param('itemdesc') ) { - if ( $cgi->param('itemdesc') eq 'Tax' ) { - push @where, "(itemdesc='Tax' OR itemdesc is null)"; - } else { - push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc')); - } -} - -if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ && $cgi->param('istax') ) { - my ( $group_op, $group_value ) = ( $1, $2 ); - if ( $group_op eq '=' ) { - #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%'); - push @where, 'itemdesc = '. dbh->quote($group_value); - } elsif ( $group_op eq '!=' ) { - push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )'; - } else { - die "guru meditation #00de: group_op $group_op\n"; - } + } elsif ( $cgi->param('out') ) { -} + push @where, 'item_tax.invnum IS NULL', + 'item_exempt.billpkgnum IS NULL'; -push @where, 'cust_bill_pkg.pkgnum != 0' if $cgi->param('nottax'); -push @where, 'cust_bill_pkg.pkgnum = 0' if $cgi->param('istax'); - -if ( $cgi->param('cust_tax') ) { - #false laziness -ish w/report_tax.cgi - my $cust_exempt; - if ( $cgi->param('taxname') ) { - my $q_taxname = dbh->quote($cgi->param('taxname')); - $cust_exempt = - "( tax = 'Y' - OR EXISTS ( SELECT 1 FROM cust_main_exemption - WHERE cust_main_exemption.custnum = cust_main.custnum - AND cust_main_exemption.taxname = $q_taxname ) - ) - "; - } else { - $cust_exempt = " tax = 'Y' "; - } + } elsif ( @tax_where ) { - push @where, $cust_exempt; -} + # union of taxable + all exempt_ cases + push @where, + '(item_tax.invnum IS NOT NULL OR item_exempt.billpkgnum IS NOT NULL)'; -my $use_usage = $cgi->param('use_usage'); - -my $count_query; -if ( $cgi->param('pkg_tax') ) { - - $count_query = - "SELECT COUNT(*), - SUM( - ( CASE WHEN part_pkg.setuptax = 'Y' - THEN cust_bill_pkg.setup - ELSE 0 - END - ) - + - ( CASE WHEN part_pkg.recurtax = 'Y' - THEN cust_bill_pkg.recur - ELSE 0 - END - ) - ) - "; - - push @where, "( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) - OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) )", - "( tax != 'Y' OR tax IS NULL )"; - -} elsif ( $cgi->param('taxable') ) { - - my $setup_taxable = "( - CASE WHEN part_pkg.setuptax = 'Y' - THEN 0 - ELSE cust_bill_pkg.setup - END - )"; - - my $recur_taxable = "( - CASE WHEN part_pkg.recurtax = 'Y' - THEN 0 - ELSE cust_bill_pkg.recur - END - )"; - - my $exempt = "( - SELECT COALESCE( SUM(amount), 0 ) FROM cust_tax_exempt_pkg - WHERE cust_tax_exempt_pkg.billpkgnum = cust_bill_pkg.billpkgnum - )"; - - $count_query = - "SELECT COUNT(*), SUM( $setup_taxable + $recur_taxable - $exempt )"; - - push @where, - #not tax-exempt package (setup or recur) - "( - ( ( part_pkg.setuptax != 'Y' OR part_pkg.setuptax IS NULL ) - AND cust_bill_pkg.setup > 0 ) - OR - ( ( part_pkg.recurtax != 'Y' OR part_pkg.recurtax IS NULL ) - AND cust_bill_pkg.recur > 0 ) - )", - #not a tax_exempt customer - "( tax != 'Y' OR tax IS NULL )"; - #not covered in full by a monthly tax exemption (texas tax) - "0 < ( $setup_taxable + $recur_taxable - $exempt )", - -} else { - - if ( $use_usage ) { - $count_query = "SELECT COUNT(*), "; - } else { - $count_query = "SELECT COUNT(DISTINCT billpkgnum), "; } + # recur/usage separation + $use_usage = $cgi->param('usage'); if ( $use_usage eq 'recurring' ) { - $count_query .= "SUM(setup + recur - usage)"; - } elsif ( $use_usage eq 'usage' ) { - $count_query .= "SUM(usage)"; - } elsif ( $unearned ) { - $count_query .= "SUM(cust_bill_pkg.recur)"; - } elsif ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) { - $count_query .= "SUM( COALESCE(cust_bill_pkg_tax_rate_location.amount, cust_bill_pkg.setup + cust_bill_pkg.recur))"; - } elsif ( $cgi->param('iscredit') eq 'rate') { - $count_query .= "SUM( cust_credit_bill_pkg.amount )"; - } else { - $count_query .= "SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)"; - } - if ( $unearned ) { + my $recur_no_usage = FS::cust_bill_pkg->charged_sql('', '', no_usage => 1); + push @select, "($recur_no_usage) AS recur_no_usage"; + $peritem[1] = 'recur_no_usage'; + $total[1] = "SUM(cust_bill_pkg.setup + $recur_no_usage)"; + $total_desc[1] .= ' (excluding usage)'; - #false laziness w/report_prepaid_income.cgi + } elsif ( $use_usage eq 'usage' ) { - my $float = 'REAL'; #'DOUBLE PRECISION'; + my $usage = FS::cust_bill_pkg->usage_sql(); + push @select, "($usage) AS _usage"; + # there's already a method named 'usage' + $peritem[1] = '_usage'; + $peritem_desc[1] = 'Usage charge'; + $total[1] = "SUM($usage)"; + $total_desc[1] .= ' usage charges'; + } - my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)"; - my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $unearned - THEN 0 - ELSE ($unearned - cust_bill_pkg.sdate) - END)"; - #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)"; +} elsif ( $cgi->param('istax') ) { - my $remaining = "(1 - $elapsed/$period)"; + @peritem = ( 'setup' ); # taxes only have setup + @peritem_desc = ( 'Tax charge' ); - $count_query .= ", SUM($remaining * cust_bill_pkg.recur)"; + push @where, 'cust_bill_pkg.pkgnum = 0'; - } + # tax location when using tax_rate_location + if ( scalar( grep( /locationtaxid/, $cgi->param ) ) ) { -} + $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '. + ' LEFT JOIN tax_rate_location USING ( taxratelocationnum )'; + push @where, FS::tax_rate_location->location_sql( + map { $_ => (scalar($cgi->param($_)) || '') } + qw( district city county state locationtaxid ) + ); -my $join_cust = ' JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) '; + $total[1] = 'SUM( + COALESCE(cust_bill_pkg_tax_rate_location.amount, + cust_bill_pkg.setup + cust_bill_pkg.recur) + )'; + } elsif ( $cgi->param('out') ) { -my $join_pkg; -if ( $cgi->param('nottax') ) { + $join_pkg = ' + LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum) + '; + push @where, 'cust_bill_pkg_tax_location.billpkgnum IS NULL'; - $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum ) - LEFT JOIN part_pkg USING ( pkgpart ) - LEFT JOIN part_pkg AS override - ON pkgpart_override = override.pkgpart '; - $join_pkg .= ' LEFT JOIN cust_location USING ( locationnum ) ' - if $conf->exists('tax-pkg_address'); + # each billpkgnum should appear only once + $total[0] = 'COUNT(*)'; + $total[1] = 'SUM(cust_bill_pkg.setup)'; -} elsif ( $cgi->param('istax') ) { + } else { # not locationtaxid or 'out'--the normal case - #false laziness w/report_tax.cgi $taxfromwhere - if ( scalar( grep( /locationtaxid/, $cgi->param ) ) || - $cgi->param('iscredit') eq 'rate') { + $join_pkg = ' + LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum) + JOIN cust_main_county USING (taxnum) + '; - $join_pkg .= - ' LEFT JOIN cust_bill_pkg_tax_rate_location USING ( billpkgnum ) '. - ' LEFT JOIN tax_rate_location USING ( taxratelocationnum ) '; + # don't double-count the components of consolidated taxes + $total[0] = 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)'; + $total[1] = 'SUM(cust_bill_pkg_tax_location.amount)'; + } - } elsif ( $conf->exists('tax-pkg_address') ) { + # taxclass + if ( $cgi->param('taxclassNULL') ) { + push @where, 'cust_main_county.taxclass IS NULL'; + } - $join_pkg .= ' LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum ) - LEFT JOIN cust_location USING ( locationnum ) '; + # taxname + if ( $cgi->param('taxnameNULL') ) { + push @where, 'cust_main_county.taxname IS NULL OR '. + 'cust_main_county.taxname = \'Tax\''; + } elsif ( $cgi->param('taxname') ) { + push @where, 'cust_main_county.taxname = '. + dbh->quote($cgi->param('taxname')); + } - #quelle kludge, somewhat false laziness w/report_tax.cgi - s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where; + # specific taxnums + if ( $cgi->param('taxnum') ) { + my $taxnum_in = join(',', + grep /^\d+$/, $cgi->param('taxnum') + ); + push @where, "cust_main_county.taxnum IN ($taxnum_in)" + if $taxnum_in; } - if ( $cgi->param('iscredit') ) { - $join_pkg .= ' JOIN cust_credit_bill_pkg USING ( billpkgnum'; - if ( $cgi->param('iscredit') eq 'rate' ) { - $join_pkg .= ', billpkgtaxratelocationnum )'; - } elsif ( $conf->exists('tax-pkg_address') ) { - $join_pkg .= ', billpkgtaxlocationnum )'; - push @where, "billpkgtaxratelocationnum IS NULL"; + # report group (itemdesc) + if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) { + my ( $group_op, $group_value ) = ( $1, $2 ); + if ( $group_op eq '=' ) { + #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%'); + push @where, 'itemdesc = '. dbh->quote($group_value); + } elsif ( $group_op eq '!=' ) { + push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )'; } else { - $join_pkg .= ' )'; - push @where, "billpkgtaxratelocationnum IS NULL"; + die "guru meditation #00de: group_op $group_op\n"; } } -} else { + # itemdesc, for some reason + if ( $cgi->param('itemdesc') ) { + if ( $cgi->param('itemdesc') eq 'Tax' ) { + push @where, "(itemdesc='Tax' OR itemdesc is null)"; + } else { + push @where, 'itemdesc='. dbh->quote($cgi->param('itemdesc')); + } + } - #die? - warn "neiether nottax nor istax parameters specified"; - #same as before? - $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum ) - LEFT JOIN part_pkg USING ( pkgpart ) '; +} # nottax / istax -} +# credit +if ( $cgi->param('credit') ) { -my $where = ' WHERE '. join(' AND ', @where); - -if ($use_usage) { - $count_query .= - " FROM (SELECT cust_bill_pkg.setup, cust_bill_pkg.recur, - ( SELECT COALESCE( SUM(amount), 0 ) FROM cust_bill_pkg_detail - WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_detail.billpkgnum - ) AS usage FROM cust_bill_pkg $join_cust $join_pkg $where - ) AS countquery"; -} else { - $count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where"; -} + my $credit_sub; + + if ( $cgi->param('istax') ) { + # then we need to group/join by billpkgtaxlocationnum, to get only the + # relevant part of partial taxes + my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount, + reason.reason as reason_text, access_user.username AS username_text, + billpkgtaxlocationnum, billpkgnum + FROM cust_credit_bill_pkg + JOIN cust_credit_bill USING (creditbillnum) + JOIN cust_credit USING (crednum) + LEFT JOIN reason USING (reasonnum) + LEFT JOIN access_user USING (usernum) + GROUP BY billpkgnum, billpkgtaxlocationnum, reason.reason, + access_user.username"; + + if ( $cgi->param('out') ) { + + # find credits that are applied to the line items, but not to + # a cust_bill_pkg_tax_location link + $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit + USING (billpkgnum)"; + push @where, 'item_credit.billpkgtaxlocationnum IS NULL'; + + } else { + + # find credits that are applied to the CBPTL links that are + # considered "interesting" by the report criteria + $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit + USING (billpkgtaxlocationnum)"; -my @select = ( 'cust_bill_pkg.*', - 'cust_bill._date', ); + } -push @select, 'part_pkg.pkg', - 'part_pkg.freq', - unless $cgi->param('istax'); + } else { + # then only group by billpkgnum + my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount, + reason.reason as reason_text, access_user.username AS username_text, + billpkgnum + FROM cust_credit_bill_pkg + JOIN cust_credit_bill USING (creditbillnum) + JOIN cust_credit USING (crednum) + LEFT JOIN reason USING (reasonnum) + LEFT JOIN access_user USING (usernum) + GROUP BY billpkgnum, reason.reason, access_user.username"; + $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)"; + } -push @select, 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(); + push @where, 'item_credit.billpkgnum IS NOT NULL'; + push @select, 'item_credit.credit_amount', + 'item_credit.username_text', + 'item_credit.reason_text'; + push @peritem, 'credit_amount', 'username_text', 'reason_text'; + push @peritem_desc, 'Credited', 'By', 'Reason'; + push @total, 'SUM(credit_amount)'; + push @total_desc, "$money_char%.2f credited"; +} # if credit + +push @select, 'cust_main.custnum', FS::UI::Web::cust_sql_fields(); + +my $where = join(' AND ', @where); +$where &&= "WHERE $where"; my $query = { 'table' => 'cust_bill_pkg', 'addl_from' => "$join_cust $join_pkg", 'hashref' => {}, - 'select' => join(', ', @select ), + 'select' => join(",\n", @select ), 'extra_sql' => $where, - 'order_by' => 'ORDER BY _date, billpkgnum', + 'order_by' => 'ORDER BY cust_bill._date, cust_bill_pkg.billpkgnum', }; -my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; -my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; +my $count_query = + 'SELECT ' . join(',', @total) . + " FROM cust_bill_pkg $join_cust $join_pkg + $where"; -my $conf = new FS::Conf; -my $money_char = $conf->config('money_char') || '$'; +shift @total_desc; #the first one is implicit -my $owed_sub = sub { - $money_char. shift->owed_recur; #_recur :/ -}; +@peritem_desc = map {emt($_)} @peritem_desc; +my @peritem_sub = map { + my $field = $_; + if ($field =~ /_text$/) { # kludge for credit reason/username fields + sub {$_[0]->get($field)}; + } else { + sub { sprintf($money_char.'%.2f', $_[0]->get($field)) } + } +} @peritem; +my @peritem_null = map { '' } @peritem; # placeholders +my $peritem_align = 'r' x scalar(@peritem); -my $payment_date_sub = sub { - #my $cust_bill_pkg = shift; - my @cust_pay = sort { $a->_date <=> $b->_date } - map $_->cust_bill_pay->cust_pay, - shift->cust_bill_pay_pkg('recur') #recur :/ - or return ''; - time2str('%b %d %Y', $cust_pay[-1]->_date ); -}; +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; +warn "\n\nQUERY:\n".Dumper($query)."\n\nCOUNT_QUERY:\n$count_query\n\n" + if $cgi->param('debug'); </%init> diff --git a/httemplate/search/cust_bill_pkg_referral.html b/httemplate/search/cust_bill_pkg_referral.html new file mode 100644 index 000000000..77b486021 --- /dev/null +++ b/httemplate/search/cust_bill_pkg_referral.html @@ -0,0 +1,304 @@ +<& elements/search.html, + 'title' => emt('Sales with advertising source'), + 'name' => emt('line items'), + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ + ($setup ? $money_char. '%.2f setup' : ()), + ($recur ? $money_char. '%.2f recurring' : ()), + ($usage ? $money_char. '%.2f usage' : ()), + ], + 'header' => [ + emt('Description'), + ($setup ? emt('Setup') : ()), + ($recur ? emt('Recurring') : ()), + ($usage ? emt('Usage') : ()), + emt('Invoice'), + emt('Invoice date'), + emt('Paid'), + emt('Payment date'), + emt('Pkg. status'), + emt('Pkg. class'), + '', #report class + emt('Cust#'), + emt('Customer'), + emt('Ad source'), + emt('Agent'), + ], + 'fields' => [ + 'pkg', + ($setup ? money_sub('setup') : ()), + ($recur ? money_sub('recur_no_usage') : ()), + ($usage ? money_sub('recur_usage') : ()), + 'invnum', + date_sub('_date'), + money_sub('paid'), + date_sub('last_pay'), + sub { + my $cust_pkg = shift->cust_pkg; + $cust_pkg ? ucfirst($cust_pkg->status) : ''; + }, + 'classname', + sub { # report_option + my $cust_bill_pkg = shift; + my $pkgpart = $cust_bill_pkg->pkgpart_override + || $cust_bill_pkg->cust_pkg->pkgpart; + if ( !exists($report_classes{$pkgpart}) ) { + my $part_pkg = FS::part_pkg->by_key($pkgpart); + my %opts = $part_pkg->options; + $report_classes{$pkgpart} = [ + map { /^report_option_(\d+)/ ? + $report_option_name{$1} : + () } + keys %opts + ]; + } + join( '<BR>', @{ $report_classes{$pkgpart} }); + }, + 'custnum', + 'name', + 'referral', # from query + 'agent', + ], + 'sort_fields' => [ + '', + ($setup ? 'setup' : ()), + ($recur ? 'recur_no_usage' : ()), + ($usage ? 'recur_usage' : ()), + 'invnum', + '_date', + 'paid', + 'last_pay', + '', #package status + 'classname', + '', #report_option + 'custnum', + '', + 'referral', + 'agent', + ], + 'links' => [ + '', #package/item desc + ('') x $x, #setup/recur/usage + $ilink, #invnum + $ilink, #invoice date + '', #paid amt + '', #payment date + '', #pkg status + '', #classnum + '', #report class + $clink, #custnum + $clink, #customer name + '', #referral + '', #agent + ], + #'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(), + 'align' => 'l' . ('r' x $x) . 'rcrccccrlll', + 'color' => [ ('') x (5 + $x), + sub { + my $cust_pkg = shift->cust_pkg; + $cust_pkg ? ucfirst($cust_pkg->statuscolor) : ''; + }, + ('') x 6, + ], + 'style' => [ + ('') x (5 + $x), + 'b', + ('') x 6 + ], +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; + +my $setup = $cgi->param('setup') ? 1 : 0; +my $recur = $cgi->param('recur') ? 1 : 0; +my $usage = $cgi->param('usage') ? 1 : 0; + +my $x = $setup + $recur + $usage; + +my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' ); +my ($join_cust, $join_pkg ) = ('', ''); + +#here is the agent virtualization +my $agentnums_sql = + $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' ); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +my @where = ( $agentnums_sql, + 'cust_bill_pkg.pkgnum != 0', # exclude taxes + "cust_bill._date >= $beginning", + "cust_bill._date <= $ending", + ); + +my @status_where; +foreach my $status ($cgi->param('status')) { + if ( $status =~ /^([- a-z]+)$/ ) { #"one-time charge" + push @status_where, "'$status'"; + } +} +if ( @status_where ) { + push @where, '('. FS::cust_pkg->status_sql. + ') IN (' . join(',', @status_where) .')'; +} + +my @refnum; +foreach my $refnum ($cgi->param('refnum')) { + if ( $refnum =~ /^\d+$/ ) { + push @refnum, $refnum; + } +} +if ( @refnum ) { + push @where, 'cust_main.refnum IN ('.join(',', @refnum).')'; +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.agentnum = $1"; +} + +#classnum +# not specified: all classes +# 0: empty class +# N: classnum +my $use_override = 1; #$cgi->param('use_override'); +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + my $comparison = ''; + if ( $1 == 0 ) { + $comparison = "IS NULL"; + } else { + $comparison = "= $1"; + } + + if ( $use_override ) { + push @where, "( + part_pkg.classnum $comparison AND pkgpart_override IS NULL OR + override.classnum $comparison AND pkgpart_override IS NOT NULL + )"; + } else { + push @where, "part_pkg.classnum $comparison"; + } +} + +# report option +my @report_option = grep /^\d+$/, ( $cgi->param('report_option') ); +if ( @report_option ) { + @report_option = map { "'report_option_$_'" } @report_option; + push @where, "EXISTS( + SELECT 1 FROM part_pkg_option WHERE optionname IN (". + join(',', @report_option).") AND ( + part_pkg_option.pkgpart = cust_pkg.pkgpart AND pkgpart_override IS NULL + OR part_pkg_option.pkgpart = pkgpart_override + ) + )"; +} + +my $setup_sql = + FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'setup'); +my $recur_sql = + FS::cust_bill_pkg->charged_sql('', '', setuprecur => 'recur', no_usage => 1); +my $usage_sql = FS::cust_bill_pkg->usage_sql; + +# exclude zero-amount items +my @orwhere; +push @orwhere, "(cust_bill_pkg.setup > 0)" if $setup; +push @orwhere, "($recur_sql > 0)" if $recur; +push @orwhere, "($usage_sql > 0)" if $usage; +push @where, '('.join(' OR ', @orwhere).')' if @orwhere; + +$join_cust = ' JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN part_referral USING ( refnum ) + LEFT JOIN agent ON cust_main.agentnum = agent.agentnum + '; + +$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + LEFT JOIN part_pkg AS override + ON pkgpart_override = override.pkgpart + LEFT JOIN pkg_class ON '; #... + +if ( $use_override ) { + # join to whichever pkgpart is appropriate + $join_pkg .= ' + ( pkgpart_override IS NULL AND part_pkg.classnum = pkg_class.classnum ) + OR ( pkgpart_override IS NOT NULL AND override.classnum = pkg_class.classnum )'; +} else { + $join_pkg .= 'part_pkg.classnum = pkg_class.classnum'; +} + +my $where = ' WHERE '. join(' AND ', @where); + +# setup and recurring only +my $count_query = "SELECT + COUNT(billpkgnum)". + ($setup ? ", SUM($setup_sql)" : ''). + ($recur ? ", SUM($recur_sql)" : ''). + ($usage ? ", SUM($usage_sql)" : ''). + " FROM cust_bill_pkg + $join_cust + $join_pkg + $where + "; + +my $paid_sql = FS::cust_bill_pkg->paid_sql('', ''); +my $last_pay_sql = "SELECT MAX(_date) + FROM cust_bill_pay JOIN cust_bill_pay_pkg USING (billpaynum) + WHERE cust_bill_pay_pkg.billpkgnum = cust_bill_pkg.billpkgnum"; + +push @select, 'part_pkg.pkg', + 'part_pkg.freq', + 'cust_main.custnum', + 'cust_main.first', + 'cust_main.last', + 'cust_main.company', + 'part_referral.referral', + "($paid_sql) AS paid", + "($last_pay_sql) AS last_pay", + "($recur_sql) AS recur_no_usage", + "($usage_sql) AS recur_usage", + 'pkg_class.classname', + 'agent.agent', + ; + +my $query = { + 'table' => 'cust_bill_pkg', + 'addl_from' => "$join_cust $join_pkg", + 'hashref' => {}, + 'select' => join(",\n", @select ), + 'extra_sql' => $where, + 'order_by' => 'ORDER BY cust_bill._date, billpkgnum', +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my %report_classes; #cache +my %report_option_name = + map { $_->num => $_->name } qsearch('part_pkg_report_option', {}); + +# should this be in Mason.pm or something? +sub money_sub { + $conf ||= new FS::Conf; + $money_char ||= $conf->config('money_char') || '$'; + my $field = shift; + sub { + $money_char . sprintf('%.2f', $_[0]->get($field)); + }; +} + +sub date_sub { + my $field = shift; + sub { + my $value = $_[0]->get($field); + $value ? time2str('%b %d %Y', $value) : ''; + }; +} + +</%init> diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html index 917257034..38f03491d 100755 --- a/httemplate/search/cust_credit.html +++ b/httemplate/search/cust_credit.html @@ -96,6 +96,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { $title = $agent->agent. " $title"; } +if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @search, "refnum = $1"; + my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } ); + die "unknown refnum $1" unless $part_referral; + $title = $part_referral->referral. " $title"; +} + if ( $unapplied ) { push @search, FS::cust_credit->unapplied_sql . ' > 0'; } diff --git a/httemplate/search/cust_credit_bill.html b/httemplate/search/cust_credit_bill.html index 7f9eb7887..9fd6a987a 100644 --- a/httemplate/search/cust_credit_bill.html +++ b/httemplate/search/cust_credit_bill.html @@ -85,6 +85,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { $title = $agent->agent. " $title"; } +if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @search, "refnum = $1"; + my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } ); + die "unknown refnum $1" unless $part_referral; + $title = $part_referral->referral. " $title"; +} + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); push @search, "cust_bill._date >= $beginning ", "cust_bill._date <= $ending"; diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html index fef295e62..4612118a2 100644 --- a/httemplate/search/cust_credit_bill_pkg.html +++ b/httemplate/search/cust_credit_bill_pkg.html @@ -167,7 +167,7 @@ if ( $cgi->param('taxclass') } -my @loc_param = qw( city county state country ); +my @loc_param = qw( district city county state country ); if ( $cgi->param('out') ) { @@ -215,7 +215,7 @@ if ( $cgi->param('out') ) { my %ph = ( 'county' => dbh->quote($_), map { $_ => dbh->quote( $cgi->param($_) ) } - qw( city state country ) + qw( district city state country ) ); my ( $loc_sql, @param ) = FS::cust_pkg->location_sql; diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html index fd87aa575..361c8ad2f 100644 --- a/httemplate/search/cust_credit_refund.html +++ b/httemplate/search/cust_credit_refund.html @@ -78,6 +78,13 @@ if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { $title = $agent->agent. " $title"; } +if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @search, "refnum = $1"; + my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } ); + die "unknown refnum $1" unless $part_referral; + $title = $part_referral->referral. " $title"; +} + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); push @search, "cust_credit._date >= $beginning ", "cust_credit._date <= $ending"; diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html index e87b21474..08800d431 100644 --- a/httemplate/search/cust_main-zip.html +++ b/httemplate/search/cust_main-zip.html @@ -4,8 +4,8 @@ 'query' => $sql_query, 'count_query' => $count_sql, 'header' => [ 'Zip code', 'Customers', ], - #'fields' => [ 'zip', 'num_cust', ], - 'links' => [ '', sub { 'somewhere'; } ], + 'fields' => [ 0, 1 ], + 'links' => [ '', $link ], ) %> <%init> @@ -63,48 +63,36 @@ sub strip_plus4 { END"; } -my( $zip, $czip); -if ( $cgi->param('column') eq 'ship_zip' ) { - - my $casewhen_noship = - "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN "; - - $czip = "$casewhen_noship zip ELSE ship_zip END"; - - if ( $cgi->param('ignore_plus4') ) { - $zip = $casewhen_noship. strip_plus4('zip'). - " ELSE ". strip_plus4('ship_zip'). ' END'; - - } else { - $zip = $casewhen_noship. fieldorempty('zip'). - " ELSE ". fieldorempty('ship_zip'). ' END'; - } +$cgi->param('column') =~ /^(bill|ship)$/; +my $location = $1 || 'bill'; +$location .= '_locationnum'; +my $zip; +if ( $cgi->param('ignore_plus4') ) { + $zip = strip_plus4('cust_location.zip'); } else { - - $czip = 'zip'; - - if ( $cgi->param('ignore_plus4') ) { - $zip = strip_plus4('zip'); - } else { - $zip = fieldorempty('zip'); - } - + $zip = fieldorempty('cust_location.zip'); } # construct the queries and send 'em off +my $join = "JOIN cust_location ON (cust_main.$location = cust_location.locationnum)"; + my $sql_query = "SELECT $zip AS zipcode, COUNT(*) AS num_cust FROM cust_main + $join $where GROUP BY zipcode - ORDER BY num_cust DESC + ORDER BY num_cust DESC, $zip ASC "; -my $count_sql = "select count(distinct $czip) from cust_main $where"; +my $count_sql = + "SELECT COUNT(DISTINCT cust_location.zip) + FROM cust_main $join $where"; -# XXX should link... +my $link = [ $p.'search/cust_main.html?zip=', + sub { $_[0]->[0] } ]; </%init> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 859ef04e6..7c3ad3384 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -81,13 +81,8 @@ <TR> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('(bill) name') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('company') |h %></TH> - -%if ( defined dbdef->table('cust_main')->column('ship_last') ) { - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('(service) name') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('company') |h %></TH> -%} + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Name') |h %></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Company') |h %></TH> %foreach my $addl_header ( @addl_headers ) { <TH CLASS="grid" BGCOLOR="#cccccc"><% $addl_header %></TH> @@ -172,25 +167,6 @@ <% $pcompany %> </TD> -% if ( defined dbdef->table('cust_main')->column('ship_last') ) { -% my($ship_last,$ship_first,$ship_company)=( -% $cust_main->ship_last || $cust_main->getfield('last'), -% $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, -% $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, -% ); -% my $pship_company = $ship_company -% ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>! -% : '<FONT SIZE=-1> </FONT>'; -% - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan %>> - <A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A> - </TD> - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan %>> - <% $pship_company %></A> - </TD> -% } -% % foreach my $addl_col ( @addl_cols ) { % if ( $addl_col eq 'tickets' ) { % if ( @custom_priorities ) { @@ -492,9 +468,10 @@ if ( $cgi->param('browse') if ( $cgi->param('search_cust') ) { $sortby = \*company_sort; $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; - push @cust_main, smart_search( 'search' => scalar($cgi->param('search_cust')), - 'no_fuzzy_on_exact' => 1, #pref? - ); + push @cust_main, smart_search( + 'search' => scalar($cgi->param('search_cust')), + 'no_fuzzy_on_exact' => ! $curuser->option('enable_fuzzy_on_exact'), + ); } @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html index 498024ba0..fa79b4dfb 100755 --- a/httemplate/search/cust_main.html +++ b/httemplate/search/cust_main.html @@ -33,9 +33,7 @@ <%init> die "access denied" - unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') && - $FS::CurrentUser::CurrentUser->access_right('List packages') - ); + unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search'); my %search_hash = (); @@ -43,11 +41,10 @@ my %search_hash = (); #scalars my @scalars = qw ( - agentnum status address paydate_year paydate_month invoice_terms + agentnum status address zip paydate_year paydate_month invoice_terms no_censustract with_geocode custbatch usernum cancelled_pkgs cust_fields flattened_pkgs - refnum ); for my $param ( @scalars ) { @@ -56,7 +53,7 @@ for my $param ( @scalars ) { } #lists -for my $param (qw( classnum payby tagnum )) { +for my $param (qw( classnum refnum payby tagnum )) { $search_hash{$param} = [ $cgi->param($param) ]; } @@ -64,14 +61,22 @@ for my $param (qw( classnum payby tagnum )) { # parse dates ### -foreach my $field (qw( signupdate )) { +foreach my $field (qw( signupdate birthdate spouse_birthdate anniversary_date )) { my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); - next if $beginning == 0 && $ending == 4294967295 && !defined($cgi->param('signuphour')); + next if $beginning == 0 && $ending == 4294967295 && ( $field ne 'signupdate' || !defined($cgi->param('signuphour')) ); #or $disable{$cgi->param('status')}->{$field}; - $search_hash{$field} = [ $beginning, $ending, $cgi->param('signuphour') ]; + unless ( $field eq 'signupdate' ) { + $beginning -= 43200; + $ending -= 43200; + } + + my @ary = ( $beginning, $ending ); + push @ary, scalar($cgi->param('signuphour')) if $field eq 'signupdate'; + + $search_hash{$field} = \@ary; } diff --git a/httemplate/search/cust_pay_pending.html b/httemplate/search/cust_pay_pending.html index 8b7350853..2afce0ce9 100755 --- a/httemplate/search/cust_pay_pending.html +++ b/httemplate/search/cust_pay_pending.html @@ -5,7 +5,6 @@ 'name_verb' => 'pending', 'disable_link' => 1, 'disable_by' => 1, #add otaker to cust_pay_pending? - 'html_init' => include('/elements/init_overlib.html'), 'addl_header' => [ 'Time', 'Payment Status', ], 'addl_fields' => [ sub { time2str('%r', shift->_date ) }, $status_sub, diff --git a/httemplate/search/cust_pkg_summary.html b/httemplate/search/cust_pkg_summary.html index f9adf044e..8c05f7382 100644 --- a/httemplate/search/cust_pkg_summary.html +++ b/httemplate/search/cust_pkg_summary.html @@ -23,6 +23,6 @@ <%init> die "access denied" - unless $curuser->access_right('Summarize packages'); + unless $FS::CurrentUser::CurrentUser->access_right('Summarize packages'); </%init> diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html index 9cf4bbda6..2adcbd76f 100644 --- a/httemplate/search/cust_svc.html +++ b/httemplate/search/cust_svc.html @@ -13,7 +13,10 @@ sub { #$_[0]->svc. ': '. $_[0]->label; my($label, $value, $svcdb) = $_[0]->label; - "$label: $value"; + my $id = $_[0]->agent_svcid + ? $_[0]->agent_svcid.': ' + : ''; + "$label: $id$value"; }, # package? \&FS::UI::Web::cust_fields, @@ -42,79 +45,75 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); -my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; +my $sql_query; -my @extra_sql = (); my $orderby = 'ORDER BY svcnum'; #has to be ordered by something #for pagination to work + if ( length( $cgi->param('search_svc') ) ) { - my $string = $cgi->param('search_svc'); - $string =~ s/(^\s+|\s+$)//; #trim leading & trailing whitespace - - # implement fuzzy searching in subclasses too at some point? - # service searching maybe shouldn't be fuzzy... - - push @extra_sql, - ' ( '. join(' OR ', - map { my $table = $_; - my $search_sql = "FS::$table"->search_sql($string); - " ( svcdb = '$table' - AND 0 < ( SELECT COUNT(*) FROM $table - WHERE $table.svcnum = cust_svc.svcnum - AND $search_sql - ) - ) "; - } - FS::part_svc->svc_tables - ). ' ) '; - -} elsif ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { - - $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb"; - push @extra_sql, "svcdb = '$1'"; - $addl_from .= " LEFT JOIN $1 USING ( svcnum ) "; - - push @extra_sql, 'pkgnum IS NULL' - if $cgi->param('magic') eq 'unlinked'; - - if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { - my $sortby = $1; - $orderby = "ORDER BY $sortby"; + $sql_query = { + FS::cust_svc->smart_search_param( + 'search' => scalar($cgi->param('search_svc')) + ) + }; + +} else { + + my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + + my @extra_sql = (); + + if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + $cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unknown svcdb"; + push @extra_sql, "svcdb = '$1'"; + $addl_from .= " LEFT JOIN $1 USING ( svcnum ) "; + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $orderby = "ORDER BY $sortby"; + } + + } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + + push @extra_sql, "svcpart = $1"; + + } else { + errorpage("No search term specified"); } -} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + #here is the agent virtualization + push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); - push @extra_sql, "svcpart = $1"; + my $extra_sql = ' WHERE '. join(' AND ', @extra_sql ); + + $sql_query = { + 'table' => 'cust_svc', + 'addl_from' => $addl_from, + 'hashref' => {}, + 'extra_sql' => $extra_sql, + }; -} else { - errorpage("No search term specified"); } -#here is the agent virtualization -push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( - 'null_right' => 'View/link unlinked services' - ); - -my $extra_sql = ' WHERE '. join(' AND ', @extra_sql ); - -my $sql_query = { - 'select' => join(', ', - 'cust_svc.*', - 'part_svc.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'table' => 'cust_svc', - 'addl_from' => $addl_from, - 'hashref' => {}, - 'extra_sql' => $extra_sql, - 'order_by' => $orderby, -}; +$sql_query->{'select'} = join(', ', + 'cust_svc.*', + 'part_svc.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ); +$sql_query->{'order_by'} = $orderby; -my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql"; +my $count_query = "SELECT COUNT(*) FROM cust_svc ". $sql_query->{addl_from}. + ' '. $sql_query->{extra_sql}; my $link = sub { my $cust_svc = shift; diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi index 3a5155ae8..1b767f846 100644 --- a/httemplate/search/cust_tax_exempt_pkg.cgi +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -103,7 +103,7 @@ my $join = " die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions'); -my @where = (); +my @where = ("exempt_monthly = 'Y'"); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); if ( $beginning || $ending ) { @@ -121,6 +121,7 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { } if ( $cgi->param('out') ) { + # wtf? how would you ever get exemptions on a non-taxable package location? push @where, " 0 = ( @@ -151,6 +152,11 @@ if ( $cgi->param('out') ) { push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') ) if $cgi->param('taxclass'); +} elsif ( $cgi->param('taxnum') ) { + + my $taxnum_in = join(',', grep /^\d+$/, $cgi->param('taxnum') ); + push @where, "taxnum IN ($taxnum_in)" if $taxnum_in; + } my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; diff --git a/httemplate/search/customer_accounting_summary.html b/httemplate/search/customer_accounting_summary.html index 8da8914c8..72a00ed95 100644 --- a/httemplate/search/customer_accounting_summary.html +++ b/httemplate/search/customer_accounting_summary.html @@ -30,6 +30,19 @@ elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { die "agentnum $agentnum not found!" unless $sel_agent; } my $title = $sel_agent ? $sel_agent->agent.' ' : ''; + +my ($refnum,$sel_part_referral); +#if ( $cgi->param('refnum') eq 'all' ) { +# $refnum = 0; +#} els +if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { + $refnum = $1; + $sel_part_referral = qsearchs('part_referral', { 'refnum' => $refnum } ); + die "refnum $refnum not found!" unless $sel_part_referral; +} +$title .= $sel_part_referral->referral.' ' + if $sel_part_referral; + $title .= 'Customer Accounting Summary Report'; my @custs = (); @@ -45,6 +58,7 @@ die "invalid status" unless $status =~ /^\w+|$/; foreach my $cust_main ( @custs ) { next unless ($status eq '' || $status eq $cust_main->status); next unless ($agentnum == 0 || $cust_main->agentnum eq $agentnum); + next unless ($refnum == 0 || $cust_main->refnum eq $refnum); push @items, 'netsales', 'cashflow'; diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html index cc1601455..eb7566494 100644 --- a/httemplate/search/elements/cust_main_dayranges.html +++ b/httemplate/search/elements/cust_main_dayranges.html @@ -108,13 +108,14 @@ my $ranges = $opt{'ranges'} ? delete($opt{'ranges'}) : [ my $range_sub = delete($opt{'range_sub'}); #or die -my $offset = 0; +my $as_of; if($cgi->param('as_of')) { - $offset = int((time - parse_datetime($cgi->param('as_of'))) / 86400); - $opt{'title'} .= ' ('.$cgi->param('as_of').')' if $offset > 0; + $as_of = parse_datetime($cgi->param('as_of')) || ''; + $opt{'title'} .= ' ('.$cgi->param('as_of').')' if $as_of; } -my $range_cols = join(',', map call_range_sub($range_sub, @$_, 'offset' => $offset ), @$ranges ); +my $range_cols = join(',', + map call_range_sub($range_sub, @$_, 'as_of' => $as_of ), @$ranges ); my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql; @@ -144,7 +145,7 @@ unless ( $cgi->param('all_customers') ) { my $negative = $cgi->param('negative') || 0; push @where, - call_range_sub($range_sub, $days, 0, 'offset' => $offset, 'no_as'=>1). + call_range_sub($range_sub, $days, 0, 'as_of' => $as_of, 'no_as'=>1). ($negative ? ' != 0' : ' > 0'); } @@ -186,7 +187,9 @@ my $sql_query = { my $total_sql = "SELECT ". - join(',', map call_range_sub( $range_sub, @$_, 'offset' => $offset, 'sum'=>1 ), @$ranges). + join(',', + map call_range_sub( $range_sub, @$_, 'as_of' => $as_of, 'sum'=>1 ), + @$ranges). " FROM cust_main $where"; my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; @@ -251,10 +254,11 @@ sub call_range_sub { my $as = $opt{'no_as'} ? '' : " AS rangecol_${startdays}_$enddays"; - my $offset = $opt{'offset'} || 0; - # Always use $offset - 1day + 1sec = the last second of that day - my $cutoff = DateTime->now->set(hour => 23, minute => 59, second => 59); - $cutoff->subtract(days => $offset); + my $as_of = $opt{'as_of'} || time; + my $cutoff = DateTime->from_epoch(epoch => $as_of, time_zone => 'local'); + $cutoff->truncate(to => 'day'); # local midnight on the report day + $cutoff->add(days => 1); # the day after that + $cutoff->subtract(seconds => 1); # the last second of the report day my $start = $cutoff->clone; $start->subtract(days => $startdays); @@ -262,7 +266,7 @@ sub call_range_sub { my $end = $cutoff->clone; $end->subtract(days => $enddays); - #warn "offset $offset (".$cutoff->epoch."), range $startdays-$enddays (".$start->epoch . '-' . ($enddays ? $end->epoch : '').")\n"; + #warn "cutoff ".$cutoff->epoch.", range $startdays-$enddays (".$start->epoch . '-' . ($enddays ? $end->epoch : '').")\n"; my $sql = &{$range_sub}( $start->epoch, $enddays ? $end->epoch : '', $cutoff->epoch ); #%opt? diff --git a/httemplate/search/elements/cust_pay_batch_top.html b/httemplate/search/elements/cust_pay_batch_top.html index ce0ee9ed4..1dcc37ac1 100644 --- a/httemplate/search/elements/cust_pay_batch_top.html +++ b/httemplate/search/elements/cust_pay_batch_top.html @@ -14,7 +14,8 @@ Download batch in format <SELECT NAME="format"> % foreach ( keys %download_formats ) { <OPTION VALUE="<%$_%>"><% $download_formats{$_} %></OPTION> % } -</SELECT> +</SELECT> +<& .select_gateway &> % } <INPUT TYPE="submit" VALUE="Download"></FORM><BR><BR></TR> % } # end of download @@ -31,7 +32,8 @@ Download batch in format <SELECT NAME="format"> 'name' => 'FileUpload', 'action' => "${p}misc/upload-batch.cgi", 'num_files' => 1, - 'fields' => [ 'batchnum', 'format' ], + 'fields' => [ 'batchnum', 'format', 'gatewaynum' ], + 'url' => $cgi->self_url, 'message' => 'Batch results uploaded.', ) %> Upload results<BR></TR> @@ -45,20 +47,22 @@ Upload results<BR></TR> <BR></TR> % if ( $fixed ) { % if ( $fixed eq 'td_eft1464' ) { # special case -<TR>Format <SELECT NAME="format"> +<TR>Upload in format <SELECT NAME="format"> <OPTION VALUE="td_eftack264">TD EFT Acknowledgement</OPTION> <OPTION VALUE="td_eftret80">TD EFT Returned Items</OPTION> -</SELECT></TR> +</SELECT> </TR> % } % else { <INPUT TYPE="hidden" NAME="format" VALUE="<% $fixed %>"> % } % } % else { -<TR>Format <SELECT NAME="format"> +<TR>Upload in format <SELECT NAME="format"> % foreach ( keys(%upload_formats) ) { <OPTION VALUE="<%$_%>"><% $upload_formats{$_} %></OPTION> % } +</SELECT> +<& .select_gateway &> % } # if $fixed <TR><INPUT TYPE="submit" VALUE="Upload"></TR> </FORM><BR> @@ -82,6 +86,26 @@ Batch is <% $statustext{$status} %><BR> <%$count%> payments batched<BR> <%$money_char%><%$total%> total in batch<BR> +<%def .select_gateway> +% if ( $show_gateways ) { + or for gateway +<& /elements/select-table.html, + empty_label => ' ', + field => 'gatewaynum', + table => 'payment_gateway', + name_col => 'label', + value_col => 'gatewaynum', + order_by => 'ORDER BY gatewaynum', + hashref => { + 'gateway_namespace' => 'Business::BatchPayment', + 'disabled' => '', + } +&> +% } +</%def> +<%shared> +my $show_gateways = FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL"); +</%shared> <%init> my %opt = @_; my $pay_batch = $opt{'pay_batch'} or return; @@ -91,7 +115,7 @@ my $payby = $pay_batch->payby; my $status = $pay_batch->status; my $curuser = $FS::CurrentUser::CurrentUser; my $batchnum = $pay_batch->batchnum; - + my $fixed = $conf->config("batch-fixed_format-$payby"); tie my %download_formats, 'Tie::IxHash', ( diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html index 002b1a4c2..c60411107 100755 --- a/httemplate/search/elements/cust_pay_or_refund.html +++ b/httemplate/search/elements/cust_pay_or_refund.html @@ -232,6 +232,13 @@ if ( $cgi->param('magic') ) { $title = $agent->agent. " $title"; } + if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { + push @search, "refnum = $1"; + my $part_referral = qsearchs('part_referral', { 'refnum' => $1 } ); + die "unknown refnum $1" unless $part_referral; + $title = $part_referral->referral. " $title"; + } + if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { push @search, "custnum = $1"; } @@ -350,6 +357,15 @@ if ( $cgi->param('magic') ) { $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + } elsif ( $cgi->param('magic') eq 'batchnum' ) { + + $cgi->param('batchnum') =~ /^(\d+)$/ + or die "illegal batchnum: ".$cgi->param('batchnum'); + + push @search, "batchnum = $1"; + + $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + } else { die "unknown search magic: ". $cgi->param('magic'); } diff --git a/httemplate/search/elements/search-csv.html b/httemplate/search/elements/search-csv.html index 9eb1b66d1..90230e6dc 100644 --- a/httemplate/search/elements/search-csv.html +++ b/httemplate/search/elements/search-csv.html @@ -27,10 +27,21 @@ % $csv->combine(@$row); #or die $csv->status; % } % -% <% $csv->string %>\ % % } +% +% if ( $opt{'footer'} and !$opt{'no_csv_header'} ) { +% my @footer; +% foreach my $item (@{ $opt{'footer'} }) { +% if ( ref($item) eq 'CODE' ) { +% $item = &{$item}(); +% } +% push @footer, $item; +% } +% $csv->combine(@footer); +<% $csv->string %>\ +% } <%init> my %args = @_; diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html index 53167c26e..d7e81282b 100644 --- a/httemplate/search/elements/search-html.html +++ b/httemplate/search/elements/search-html.html @@ -134,9 +134,9 @@ % and !$opt{'disable_download'} % and $type ne 'html-print' ) { - <TD ALIGN="right"> + <TD ALIGN="right" CLASS="noprint"> - Download full results<BR> + <% $opt{'download_label'} || 'Download full results' %><BR> % $cgi->param('_type', "$xlsname.xls" ); as <A HREF="<% "$self_url?". $cgi->query_string %>">Excel spreadsheet</A><BR> @@ -337,6 +337,11 @@ % map { % if ( ref($_) eq 'CODE' ) { % &{$_}($row); +% } elsif ( ref($row) eq 'ARRAY' and +% $_ =~ /^\d+$/ ) { +% # for the 'straight SQL' case: specify fields +% # by position +% $row->[$_]; % } else { % $row->$_(); % } @@ -345,7 +350,8 @@ % % ) { % -% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid'; +%# my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid'; +% my $class = 'grid'; % % my $align = $aligns ? shift @$aligns : ''; % $align = " ALIGN=$align" if $align; diff --git a/httemplate/search/elements/search-xls.html b/httemplate/search/elements/search-xls.html index 0b5636c0e..94d88b096 100644 --- a/httemplate/search/elements/search-xls.html +++ b/httemplate/search/elements/search-xls.html @@ -7,14 +7,20 @@ my $header = $args{'header'}; my $rows = $args{'rows'}; my %opt = %{ $args{'opt'} }; +my $override = scalar(@$rows) >= 65536 ? 'XLSX' : ''; + +my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override); + +my $filename = $opt{'name'} || PL($opt{'name_singular'}); +$filename .= $format->{extension}; + #http_header('Content-Type' => 'application/excel' ); #eww #http_header('Content-Type' => 'application/msexcel' ); #alas #http_header('Content-Type' => 'application/x-msexcel' ); #? #http://support.microsoft.com/kb/199841 -http_header('Content-Type' => 'application/vnd.ms-excel' ); -http_header('Content-Disposition' => - 'attachment;filename="'.($opt{'name'} || PL($opt{'name_singular'}) ).'.xls"'); +http_header('Content-Type' => $format->{mime_type} ); +http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); #http://support.microsoft.com/kb/812935 #http://support.microsoft.com/kb/323308 @@ -22,8 +28,8 @@ $HTML::Mason::Commands::r->headers_out->{'Cache-control'} = 'max-age=0'; my $data = ''; my $XLS = new IO::Scalar \$data; -my $workbook = Spreadsheet::WriteExcel->new($XLS) - or die "Error opening .xls file: $!"; +my $workbook = $format->{class}->new($XLS) + or die "Error opening Excel file: $!"; my $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); @@ -42,14 +48,22 @@ my $default_format = $workbook->add_format(locked => 0); my %money_format; my $money_char = FS::Conf->new->config('money_char') || '$'; +my %date_format; +xl_parse_date_init(); + my $writer = sub { # Wrapper for $worksheet->write. # Do any massaging of the value/format here. my ($r, $c, $value, $format) = @_; - if ( $value =~ /^\Q$money_char\E(\d+\.?\d*)$/ ) { + # convert HTML entities + # both Spreadsheet::WriteExcel and Excel::Writer::XLSX accept UTF-8 strings + $value = decode_entities($value); + + if ( $value =~ /^\Q$money_char\E(-?\d+\.?\d*)$/ ) { # Currency: strip the symbol, clone the requested format, # and format it for currency $value = $1; +# warn "formatting $value as money\n"; if ( !exists($money_format{$format}) ) { $money_format{$format} = $workbook->add_format(); $money_format{$format}->copy($format); @@ -57,6 +71,22 @@ my $writer = sub { } $format = $money_format{$format}; } + elsif ( $value =~ /^([A-Z][a-z]{2}) (\d{2}) (\d{4})$/ ) { + # Date: convert the value to an Excel date number and set + # the format + $value = xl_parse_date($value); +# warn "formatting $value as date\n"; + if ( !exists($date_format{$format}) ) { + $date_format{$format} = $workbook->add_format(); + $date_format{$format}->copy($format); + $date_format{$format}->set_num_format('mmm dd yyyy'); + } + $format = $date_format{$format}; + } + else { + # String: replace line breaks with newlines + $value =~ s/<BR>/\n/gi; + } $worksheet->write($r, $c, $value, $format); }; @@ -104,6 +134,17 @@ foreach my $row ( @$rows ) { } +if ( $opt{'footer'} ) { + $r++; + $c = 0; + foreach my $item (@{ $opt{'footer'} }) { + if ( ref($item) eq 'CODE' ) { + $item = &{$item}(); + } + $writer->( $r, $c++, $item, $header_format ); + } +} + $workbook->close();# or die "Error creating .xls file: $!"; http_header('Content-Length' => length($data) ); diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 9bc66b6fa..eca68a2f8 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -162,7 +162,11 @@ Example: # Excel-specific listref of ( hashrefs or coderefs ) # each hashref: http://search.cpan.org/dist/Spreadsheet-WriteExcel/lib/Spreadsheet/WriteExcel.pm#Format_methods_and_Format_properties 'xls_format' => => [], - + + + # miscellany + 'download_label' => 'Download this report', + # defaults to 'Download full results' &> </%doc> diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi index b2a15ef3d..aeaa012f4 100755 --- a/httemplate/search/pay_batch.cgi +++ b/httemplate/search/pay_batch.cgi @@ -14,11 +14,13 @@ 'Type', 'First Download', 'Last Upload', - 'Item Count', - 'Amount', + '', # requests + '', # req amt + '', # payments + '', # pay amt 'Status', ], - 'align' => 'rcllrrc', + 'align' => 'rcllrrrrc', 'fields' => [ 'batchnum', sub { FS::payby->shortname(shift->payby); @@ -46,30 +48,44 @@ } }, sub { - my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $c = FS::cust_pay_batch->count('batchnum = '.$_[0]->batchnum); + $c ? "$c requested" : '' + }, + sub { + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; my $sth = dbh->prepare($st) or die dbh->errstr. "doing $st"; $sth->execute - or die "Error executing \"$st\": ". $sth->errstr; - $sth->fetchrow_arrayref->[0]; - }, - sub { - my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + $total ? $money_char.sprintf('%.2f',$total) : ''; + }, + sub { + my $c = FS::cust_pay->count('batchnum = '.$_[0]->batchnum); + $c ? "$c paid" : '' + }, + sub { + my $st = "SELECT SUM(paid) from cust_pay WHERE batchnum=" . shift->batchnum; my $sth = dbh->prepare($st) - or die dbh->errstr. "doing $st"; + or die dbh->errstr. "doing $st"; $sth->execute - or die "Error executing \"$st\": ". $sth->errstr; - $sth->fetchrow_arrayref->[0]; - }, + or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + $total ? $money_char.sprintf('%.2f',$total) : ''; + }, sub { $statusmap{shift->status}; }, ], 'links' => [ - $link, + '', '', - sub { shift->status eq 'O' ? $link : '' }, - sub { shift->status eq 'I' ? $link : '' }, + sub { shift->status eq 'O' ? $cpb_link : '' }, + sub { shift->status eq 'I' ? $cpb_link : '' }, + $cpb_link, + $cpb_link, + $pay_link, + $pay_link, ], 'size' => [ '', @@ -84,9 +100,42 @@ sub { shift->status eq 'I' ? "b" : '' }, ], 'html_init' => $html_init, + 'html_foot' => include('.upload_incoming'), ) - %> +<%def .upload_incoming> +% if ( FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL") > 0 ) { +<& /elements/form-file_upload.html, + name => 'FileUpload', + action => $p.'misc/upload-batch.cgi', + num_files => 1, + fields => [ 'gatewaynum' ], + message => 'Incoming batch uploaded.', +&> +<BR> +<BR> +Upload incoming batch from gateway +<& /elements/select-table.html, + table => 'payment_gateway', + field => 'gatewaynum', + name_col => 'label', + value_col => 'gatewaynum', + order_by => 'ORDER BY gatewaynum', + empty_label => ' ', + hashref => + { 'gateway_namespace' => 'Business::BatchPayment', + 'disabled' => '' }, +&> +<BR> +<& '/elements/file-upload.html', + field => 'file', + label => 'Filename', + no_table => 1, +&> +<INPUT TYPE="submit" VALUE="Upload"> +</FORM> +% } +</%def> <%init> die "access denied" @@ -130,11 +179,14 @@ push @where, my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; -my $link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ]; +my $cpb_link = [ "${p}search/cust_pay_batch.cgi?dcln=1;batchnum=", 'batchnum' ]; +my $pay_link = [ "${p}search/cust_pay.html?magic=batchnum;batchnum=", 'batchnum' ]; my $resolved = $cgi->param('resolved') || 0; $cgi->param('resolved' => !$resolved); my $html_init = '<A HREF="' . $cgi->self_url . '"><I>'. ($resolved ? 'Hide' : 'Show') . ' resolved batches</I></A><BR>'; +my $money_char = FS::Conf->new->config('money_char') || '$'; + </%init> diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/prepaid_income.html index 2fe5b6f10..ebac5a2a9 100644 --- a/httemplate/search/report_prepaid_income.cgi +++ b/httemplate/search/prepaid_income.html @@ -56,9 +56,11 @@ <BR> <% $actual_label %><% $actual_label ? 'u' : 'U' %>nearned revenue -is the amount of unearned revenue +is the as-yet-unearned portion of revenue <% $actual_label ? 'Freeside has actually' : '' %> -invoiced for packages with longer-than monthly terms. +invoiced for packages with +<% $cgi->param('include_monthly') ? 'terms extending into the future.' + : 'longer-than monthly terms.' %> % if ( $legacy ) { <BR><BR> @@ -82,13 +84,25 @@ my $actual_label = $legacy ? 'Actual ' : ''; #doesn't yet deal with daily/weekly packages +my $mode = $cgi->param('mode'); + my $time = time; my $now = $cgi->param('date') && parse_datetime($cgi->param('date')) || $time; $now =~ /^(\d+)$/ or die "unparsable date?"; $now = $1; -my $link = "cust_bill_pkg.cgi?nottax=1;unearned_now=$now"; +my $dt = DateTime->from_epoch(epoch => $now, time_zone => 'local'); +$dt->truncate(to => 'day'); # local midnight on the report day +$dt->add(days => 1); # the day after that +$dt->subtract(seconds => 1); # the last second of the report day +$now = $dt->epoch; + +my $link = "unearned_detail.html?date=$now;mode=$mode"; + +if ( $cgi->param('include_monthly') ) { + $link .= ';include_monthly=1'; +} my $curuser = $FS::CurrentUser::CurrentUser; @@ -108,6 +122,13 @@ my @where = (); #here is the agent virtualization push @where, $curuser->agentnums_sql( 'table'=>'cust_main' ); +my $status = ''; +if ( $cgi->param('status') =~ /^([a-z]+)$/ ) { + $status = $1; + $link .= ";status=$status"; + push @where, FS::cust_main->cust_status_sql . " = '$status'"; +} + my %total = (); my %total_legacy = (); foreach my $agentnum (@agentnums) { @@ -117,66 +138,54 @@ foreach my $agentnum (@agentnums) { my( $total, $total_legacy ) = ( 0, 0 ); - # my @cust_bill_pkg = - # grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[hdw])$/ } - # qsearch({ - # 'select' => 'cust_bill_pkg.*', - # 'table' => 'cust_bill_pkg', - # 'addl_from' => ' LEFT JOIN cust_bill USING ( invnum ) '. - # ' LEFT JOIN cust_main USING ( custnum ) ', - # 'hashref' => { - # 'recur' => { op=>'!=', value=>0 }, - # 'sdate' => { op=>'<', value=>$now }, - # 'edate' => { op=>'>', value=>$now }, - # }, - # 'extra_sql' => $where, - # }); - # - # foreach my $cust_bill_pkg ( @cust_bill_pkg) { - # my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate; - # - # my $elapsed = $now - $cust_bill_pkg->sdate; - # $elapsed = 0 if $elapsed < 0; - # - # my $remaining = 1 - $elapsed/$period; - # - # my $unearned = $remaining * $cust_bill_pkg->recur; - # $total += $unearned; - # - # } - - #re-written in sql: - - #false laziness w/cust_bill_pkg.cgi - - my $float = 'REAL'; #'DOUBLE PRECISION'; - - my $period = "CAST(cust_bill_pkg.edate - cust_bill_pkg.sdate AS $float)"; - my $elapsed = "(CASE WHEN cust_bill_pkg.sdate > $now - THEN 0 - ELSE ($now - cust_bill_pkg.sdate) - END)"; - #my $elapsed = "CAST($unearned - cust_bill_pkg.sdate AS $float)"; - - my $remaining = "(1 - $elapsed/$period)"; - - my $select = "SUM($remaining * cust_bill_pkg.recur)"; - - #[...] - - my $sql = "SELECT $select FROM cust_bill_pkg - LEFT JOIN cust_pkg USING ( pkgnum ) - LEFT JOIN part_pkg USING ( pkgpart ) - LEFT JOIN cust_main USING ( custnum ) - WHERE pkgpart > 0 - AND sdate < $now - AND edate > $now - AND cust_bill_pkg.recur != 0 - AND part_pkg.freq != '0' + my @opt = ($now, '', setuprecur => 'recur', no_usage => 1); + # balance owed, recurring only, not including usage charges + my $unearned_base; + if ( $mode eq 'billed' ) { + $unearned_base = '( ' . + FS::cust_bill_pkg->charged_sql(@opt) . ' - ' . + FS::cust_bill_pkg->credited_sql(@opt) . ' )'; + } elsif ( $mode eq 'paid' ) { + $unearned_base = FS::cust_bill_pkg->paid_sql(@opt); + } + + my $edate_zero = midnight_sql('edate'); + my $sdate_zero = midnight_sql('sdate'); + my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )"; + my $remaining = "GREATEST( + CAST( ($edate_zero - $now) / 86400.0 AS DECIMAL(10,0) ), + 0)"; + my $fraction = "$remaining / $period"; + + my $unearned_sql = "CAST( + GREATEST( $unearned_base * $fraction, 0 ) + AS DECIMAL(10,2) + )"; + + my $select = "SUM( $unearned_sql )"; + + if ( !$cgi->param('include_monthly') ) { + # all except freq != 0; one-time charges should never be included + $where .= " AND part_pkg.freq != '1' AND part_pkg.freq NOT LIKE '%h' AND part_pkg.freq NOT LIKE '%d' - AND part_pkg.freq NOT LIKE '%w' + AND part_pkg.freq NOT LIKE '%w'"; + } + + # $mode actually doesn't matter here, since unpaid invoices have zero + # unearned revenue + + my $sql = + "SELECT $select FROM cust_bill_pkg + LEFT JOIN cust_pkg ON (cust_bill_pkg.pkgnum = cust_pkg.pkgnum) + LEFT JOIN part_pkg USING ( pkgpart ) + LEFT JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum) + WHERE pkgpart > 0 + AND cust_bill_pkg.sdate < $now + AND cust_bill_pkg.edate > $now + AND cust_bill_pkg.recur != 0 + AND part_pkg.freq != '0' $where "; diff --git a/httemplate/search/quotation.html b/httemplate/search/quotation.html new file mode 100755 index 000000000..259c85c22 --- /dev/null +++ b/httemplate/search/quotation.html @@ -0,0 +1,268 @@ +<& elements/search.html, + 'title' => emt('Quotation Search Results'), + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'quotations', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => $count_addl, + 'redirect' => $link, + 'header' => [ emt('Quotation #'), + emt('Setup'), + emt('Recurring'), + emt('Date'), + emt('Prospect'), + emt('Customer'), + ], + 'fields' => [ + 'quotationnum', + sub { $money_char. shift->total_setup }, + sub { $money_char. shift->total_recur }, + sub { time2str('%b %d %Y', shift->_date ) }, + sub { my $prospect_main = shift->prospect_main; + $prospect_main ? $prospect_main->name : ''; + }, + sub { my $cust_main = shift->cust_main; + $cust_main ? $cust_main->name : ''; + }, + #\&FS::UI::Web::cust_fields, + ], + 'sort_fields' => [ + 'quotationnum', + '', #FS::quotation->total_setup_sql, + '', #FS::quotation->total_recur_sql, + '_date', + '', + '', + ], + 'align' => 'rrrrll', #.FS::UI::Web::cust_aligns(), + 'links' => [ + $link, + $link, + $link, + $link, + $prospect_link, + $cust_link, + #( map { $_ ne 'Cust. Status' ? $clink : '' } + # FS::UI::Web::cust_header() + #), + ], +# 'color' => [ +# '', +# '', +# '', +# '', +# '', +# FS::UI::Web::cust_colors(), +# ], +# 'style' => [ +# '', +# '', +# '', +# '', +# '', +# FS::UI::Web::cust_styles(), +# ], +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('List quotations'); + +my $join_prospect_main = 'LEFT JOIN prospect_main USING ( prospectnum )'; +my $join_cust_main = 'LEFT JOIN cust_main ON ( quotation.custnum = cust_main.custnum )'; + +#here is the agent virtualization +my $agentnums_sql = ' ( '. $curuser->agentnums_sql( table=>'prospect_main' ). + ' OR '. $curuser->agentnums_sql( table=>'cust_main' ). + ' ) '; + +my( $count_query, $sql_query ); +my $count_addl = ''; +my %search; + +#if ( $cgi->param('quotationnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { +# +# my $where = "WHERE quotationnum = $2 AND $agentnums_sql"; +# +# $count_query = "SELECT COUNT(*) FROM quotation $join_prospect_main $join_cust_main $where"; +# +# $sql_query = { +# 'table' => 'quotation', +# 'addl_from' => "$join_prospect_main $join_cust_main", +# 'hashref' => {}, +# 'extra_sql' => $where, +# }; +# +#} else { + + #some false laziness w/cust_bill::re_X + my $orderby = 'ORDER BY quotation._date'; + + if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $search{'agentnum'} = $1; + } + +# if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { +# $search{'refnum'} = $1; +# } + + if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { + $search{'prospectnum'} = $1; + } + + if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $search{'custnum'} = $1; + } + + # begin/end/beginning/ending + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, ''); + $search{'_date'} = [ $beginning, $ending ] + unless $beginning == 0 && $ending == 4294967295; + + if ( $cgi->param('quotationnum_min') =~ /^\s*(\d+)\s*$/ ) { + $search{'quotationnum_min'} = $1; + } + if ( $cgi->param('quotationnum_max') =~ /^\s*(\d+)\s*$/ ) { + $search{'quotationnum_max'} = $1; + } + + #amounts + $search{$_} = [ FS::UI::Web::parse_lt_gt($cgi, $_) ] + foreach qw( total_setup total_recur ); + +# my($query) = $cgi->keywords; +# if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { +# $search{'open'} = 1 if $1; +# ($search{'days'}, my $field) = ($2, $3); +# $field = "_date" if $field eq 'date'; +# $orderby = "ORDER BY cust_bill.$field"; +# } + +# if ( $cgi->param('newest_percust') ) { +# $search{'newest_percust'} = 1; +# $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; +# } + + my $extra_sql = ' WHERE '. FS::quotation->search_sql_where( \%search ); + + unless ( $count_query ) { + $count_query = 'SELECT COUNT(*)'; + } + $count_query .= " FROM quotation $join_prospect_main $join_cust_main $extra_sql"; + + $sql_query = { + 'table' => 'quotation', + 'addl_from' => "$join_prospect_main $join_cust_main", + 'hashref' => {}, + 'select' => join(', ', + 'quotation.*', + #( map "cust_main.$_", qw(custnum last first company) ), + 'prospect_main.prospectnum as prospect_main_prospectnum', + 'cust_main.custnum as cust_main_custnum', + #FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $extra_sql, + 'order_by' => $orderby, + }; + +#} + +my $link = [ "${p}view/quotation.html?", 'quotationnum', ]; +my $prospect_link = sub { + my $quotation = shift; + $quotation->prospect_main_prospectnum + ? [ "${p}view/prospect_main.html?", 'prospectnum' ] + : ''; +}; + +my $cust_link = sub { + my $quotation = shift; + $quotation->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $html_init = join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ keys %search ], + "../misc/${_}invoices.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $_, #key + ), + qq!<FORM NAME="${_}form">!, + ( map { my $f = $_; + my @values = ref($search{$f}) ? @{ $search{$f} } : $search{$f}; + map qq!<INPUT TYPE="hidden" NAME="$f" VALUE="$_">!, @values; + } + keys %search + ), + qq!</FORM>! +} qw( print_ email_ fax_ ftp_ spool_ ) ). + +'<SCRIPT TYPE="text/javascript"> + +function confirm_print_process() { + if ( ! confirm('.js_mt("Are you sure you want to reprint these invoices?").') ) { + return; + } + print_process(); +} +function confirm_email_process() { + if ( ! confirm('.js_mt("Are you sure you want to re-email these invoices?").') ) { + return; + } + email_process(); +} +function confirm_fax_process() { + if ( ! confirm('.js_mt("Are you sure you want to re-fax these invoices?").') ) { + return; + } + fax_process(); +} +function confirm_ftp_process() { + if ( ! confirm('.js_mt("Are you sure you want to re-FTP these invoices?").') ) { + return; + } + ftp_process(); +} +function confirm_spool_process() { + if ( ! confirm('.js_mt("Are you sure you want to re-spool these invoices?").') ) { + return; + } + spool_process(); +} + +</SCRIPT>'; + +my $menubar = []; + +#if ( $curuser->access_right('Resend quotations') ) { +# +# push @$menubar, emt('Print these invoices') => +# "javascript:confirm_print_process()", +# emt('Email these invoices') => +# "javascript:confirm_email_process()"; +# +# push @$menubar, emt('Fax these invoices') => +# "javascript:confirm_fax_process()" +# if $conf->exists('hylafax'); +# +# push @$menubar, emt('FTP these invoices') => +# "javascript:confirm_ftp_process()" +# if $conf->exists('cust_bill-ftpformat'); +# +# push @$menubar, emt('Spool these invoices') => +# "javascript:confirm_spool_process()" +# if $conf->exists('cust_bill-spoolformat'); +# +#} + +</%init> diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html index c9d97c5eb..f593a94d8 100755 --- a/httemplate/search/report_477.html +++ b/httemplate/search/report_477.html @@ -17,6 +17,18 @@ ) %> +% # not tr-select-state, we only want to choose from among those that +% # have customers + <& /elements/tr-select-table.html, + 'label' => 'State', + 'field' => 'state', + 'table' => 'cust_location', + 'name_col' => 'state', + 'value_col' => 'state', + 'disable_empty' => 1, + 'records' => \@states, + &> + <% include( '/elements/tr-select-pkg_class.html', 'multiple' => 1, 'empty_label' => '(empty class)', @@ -252,4 +264,10 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List packages'); +my @states = qsearch({ + 'table' => 'cust_location', + 'select' => 'DISTINCT(state)', + 'hashref' => { 'country' => 'US' }, # 477 report isn't relevant elsewhere +}); + </%init> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html index e3418a7d4..0e1693b9c 100644 --- a/httemplate/search/report_cdr.html +++ b/httemplate/search/report_cdr.html @@ -24,9 +24,12 @@ <SELECT NAME="freesidestatus"> <OPTION VALUE="">(all)</OPTION> <OPTION VALUE="NULL">unprocessed</OPTION> +%# <OPTION VALUE="processing-tiered">processing</OPTION> <OPTION VALUE="rated">prerated - <OPTION VALUE="done">processed</OPTION> - <OPTION VALUE="failed">skipped</OPTION> + <OPTION VALUE="no-charge">processed (included)</OPTION> + <OPTION VALUE="done">processed (billed)</OPTION> + <OPTION VALUE="skipped">skipped</OPTION> + <OPTION VALUE="failed">failed</OPTION> </SELECT> </TD> </TR> diff --git a/httemplate/search/report_cust_bill_pkg_referral.html b/httemplate/search/report_cust_bill_pkg_referral.html new file mode 100644 index 000000000..b4716d4fc --- /dev/null +++ b/httemplate/search/report_cust_bill_pkg_referral.html @@ -0,0 +1,66 @@ +<% include('/elements/header.html', 'Sales Report with Advertising Source' ) %> + +<FORM ACTION="cust_bill_pkg_referral.html" METHOD="GET"> + +<TABLE> + +<& /elements/tr-input-beginning_ending.html &> + +<& /elements/tr-select-agent.html, + 'label' => 'For agent: ', + 'disable_empty' => 0, + 'empty_label' => 'all', +&> + +<& /elements/tr-select-cust_pkg-status.html, + 'label' => 'Package status', + 'multiple' => 1, + 'disable_empty' => 1, +&> + +<& /elements/tr-select-part_referral.html, + 'multiple' => 1, + 'disable_empty' => 1, +&> + +<& /elements/tr-select-pkg_class.html, + 'pre_options' => [ '' => 'all', '0' => '(empty class)' ], + 'disable_empty' => 1, +&> + +<& /elements/tr-select-table.html, + 'label' => 'Report classes', + 'table' => 'part_pkg_report_option', + 'name_col' => 'name', + 'hashref' => { disabled => '' }, + 'element_name' => 'report_option', + 'multiple' => 1, +&> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="setup" VALUE="1" CHECKED></TD> + <TD>Show setup/one-time fees</TD> +</TR> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="recur" VALUE="1" CHECKED></TD> + <TD>Show recurring fees</TD> +</TR> + +<TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="usage" VALUE="1" CHECKED></TD> + <TD>Show usage charges</TD> +</TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Display"> +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html index 00cb9ed2c..8bad332a9 100644 --- a/httemplate/search/report_cust_main-zip.html +++ b/httemplate/search/report_cust_main-zip.html @@ -8,8 +8,8 @@ <TD ALIGN="right">Billing or service zip</TD> <TD> <SELECT NAME="column"> - <OPTION VALUE="zip">Billing zip - <OPTION VALUE="ship_zip">Service zip + <OPTION VALUE="bill">Billing zip + <OPTION VALUE="ship">Service zip </SELECT> </TD> </TR> diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html index 0ef5a5196..3e7181d4f 100755 --- a/httemplate/search/report_cust_main.html +++ b/httemplate/search/report_cust_main.html @@ -25,10 +25,22 @@ 'all_selected' => 1, &> + <& /elements/tr-select-part_referral.html, + 'label' => emt('Advertising Source'), + 'multiple' => 1, + #no, causes customers with disabled ones to disappear + #'all_selected' => 1, + &> + <TR> <TD ALIGN="right" VALIGN="center"><% mt('Address') |h %></TD> <TD><INPUT TYPE="text" NAME="address" SIZE=54></TD> </TR> + + <TR> + <TD ALIGN="right" VALIGN="center"><% mt('Zip') |h %></TD> + <TD><INPUT TYPE="text" NAME="zip" SIZE=12></TD> + </TR> <TR> <TD ALIGN="right" VALIGN="center"><% mt('Signup date') |h %></TD> @@ -42,6 +54,48 @@ </TD> </TR> +% if ( $conf->exists('cust_main-enable_birthdate') ) { + <TR> + <TD ALIGN="right" VALIGN="center"><% mt('Date of Birth') |h %></TD> + <TD> + <TABLE> + <& /elements/tr-input-beginning_ending.html, + prefix => 'birthdate', + layout => 'horiz', + &> + </TABLE> + </TD> + </TR> +% } + +% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) { + <TR> + <TD ALIGN="right" VALIGN="center"><% mt('Spouse Date of Birth') |h %></TD> + <TD> + <TABLE> + <& /elements/tr-input-beginning_ending.html, + prefix => 'spouse_birthdate', + layout => 'horiz', + &> + </TABLE> + </TD> + </TR> +% } + +% if ( $conf->exists('cust_main-enable_anniversary_date') ) { + <TR> + <TD ALIGN="right" VALIGN="center"><% mt('Anniversary Date') |h %></TD> + <TD> + <TABLE> + <& /elements/tr-input-beginning_ending.html, + prefix => 'anniversary_date', + layout => 'horiz', + &> + </TABLE> + </TD> + </TR> +% } + <& /elements/tr-select-cust_tag.html, 'cgi' => $cgi, 'is_report' => 1, @@ -101,11 +155,6 @@ &> <TR> - <TD ALIGN="right" VALIGN="center"><% mt('Include cancelled packages') |h %></TD> - <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD> - </TR> - - <TR> <TD ALIGN="right" VALIGN="center"><% mt('Without census tract') |h %></TD> <TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD> </TR> @@ -132,6 +181,12 @@ <TD ALIGN="right" VALIGN="center"><% mt('Add package columns') |h %></TD> <TD><INPUT TYPE="checkbox" NAME="flattened_pkgs"></TD> </TR> + + <TR> + <TD ALIGN="right" VALIGN="center"><% mt('Include cancelled packages') |h %></TD> + <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD> + </TR> + </TABLE> <BR> @@ -143,9 +198,7 @@ <%init> die "access denied" - unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') && - $FS::CurrentUser::CurrentUser->access_right('List packages') - ); + unless $FS::CurrentUser::CurrentUser->access_right('Advanced customer search'); my $conf = new FS::Conf; diff --git a/httemplate/search/report_customer_accounting_summary.html b/httemplate/search/report_customer_accounting_summary.html index d20f756c8..f2a13a27b 100755 --- a/httemplate/search/report_customer_accounting_summary.html +++ b/httemplate/search/report_customer_accounting_summary.html @@ -11,6 +11,14 @@ ) %> + <% include( '/elements/tr-select-part_referral.html', + 'curr_value' => scalar( $cgi->param('refnum') ), + 'label' => 'Advertising source ', + 'disable_empty' => 0, + 'empty_label' => 'all', + ) + %> + <% include('/elements/tr-select-from_to.html' ) %> <% include( '/elements/tr-select-cust_main-status.html', diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html index 061b24c68..4743e2d21 100644 --- a/httemplate/search/report_prepaid_income.html +++ b/httemplate/search/report_prepaid_income.html @@ -2,7 +2,7 @@ <% include('/elements/init_calendar.html') %> -<FORM ACTION="report_prepaid_income.cgi" METHOD="GET"> +<FORM ACTION="prepaid_income.html" METHOD="GET"> <TABLE BGCOLOR="#cccccc" CELLSPACING=0> @@ -13,7 +13,7 @@ </TR> <TR> - <TD>As of </TD> + <TD ALIGN="right">As of </TD> <TD> <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now"> <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date"> @@ -30,7 +30,20 @@ </TR> <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %> - + + <& /elements/tr-select-cust_main-status.html, + label => mt('Customer Status') &> + <& /elements/tr-select.html, + label => 'Invoice Status', + field => 'mode', + options => [ qw(billed paid) ] &> + <TR> + <TD ALIGN="right"> + <INPUT TYPE="checkbox" NAME="include_monthly" VALUE=1> + </TD> + <TD ALIGN="left"><% mt('Include packages with period ≤ 1 month') %> + </TD> + </TR> <TR> <TD COLSPAN=2> </TD> </TR> diff --git a/httemplate/search/report_quotation.html b/httemplate/search/report_quotation.html new file mode 100644 index 000000000..1be904dc3 --- /dev/null +++ b/httemplate/search/report_quotation.html @@ -0,0 +1,75 @@ +<& /elements/header.html, mt($title, @title_arg) &> + +<FORM ACTION="quotation.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> +<INPUT TYPE="hidden" NAME="prospectnum" VALUE="<% $prospectnum %>"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0 + +% unless ( $custnum ) { + <& /elements/tr-select-agent.html, + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => emt('Quotations for agent: '), + 'disable_empty' => 0, + &> +% } + + <& /elements/tr-input-beginning_ending.html &> + + <& /elements/tr-input-lessthan_greaterthan.html, + label => emt('Setup'), + field => 'total_setup', + &> + + <& /elements/tr-input-lessthan_greaterthan.html, + label => emt('Recurring'), + field => 'total_recur', + &> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<& /elements/footer.html &> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List quotations'); + +my $conf = new FS::Conf; + +my $title = 'Quotation Report'; +#false laziness w/report_cust_pkg.html +my @title_arg = (); + +my $prospectnum = ''; +my $prospect_main = ''; +if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { + $prospectnum = $1; + $prospect_main = qsearchs({ + 'table' => 'prospect_main', + 'hashref' => { 'prospectnum' => $prospectnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }) or die "unknown prospectnum $prospectnum"; + $title .= ': [_1]'; + push @title_arg, $prospect_main->name; +} + +my $custnum = ''; +my $cust_main = ''; +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $custnum = $1; + $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }) or die "unknown custnum $custnum"; + $title .= ': [_1]'; + push @title_arg, $cust_main->name; +} + +</%init> diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html index 19f863525..5cff0f4fc 100755 --- a/httemplate/search/report_receivables.html +++ b/httemplate/search/report_receivables.html @@ -52,7 +52,7 @@ function toggle(obj) { <& /elements/tr-input-date-field.html, { 'name' => 'as_of', 'value' => time, - 'label' => emt('As of date '), + 'label' => emt('At the end of date '), 'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y', } &> diff --git a/httemplate/search/report_rt_ticket.html b/httemplate/search/report_rt_ticket.html index f0d7a4200..a4ceaa6a4 100644 --- a/httemplate/search/report_rt_ticket.html +++ b/httemplate/search/report_rt_ticket.html @@ -59,7 +59,6 @@ if ( @pkgparts ) { } # get a list of TimeValue-type custom fields -RT::Init(); my $CurrentUser = RT::CurrentUser->new(); $CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username); die "RT not configured" unless $CurrentUser->id; diff --git a/httemplate/search/report_sqlradius_usage.html b/httemplate/search/report_sqlradius_usage.html new file mode 100644 index 000000000..01215e834 --- /dev/null +++ b/httemplate/search/report_sqlradius_usage.html @@ -0,0 +1,40 @@ +<& /elements/header.html, mt($title) &> + +<FORM ACTION="sqlradius_usage.html" METHOD="GET"> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0 + +<& /elements/tr-select-agent.html, + 'empty_label' => 'all', +&> + +% my @exporttypes = map { "'$_'" } qw(sqlradius broadband_sqlradius); +<& /elements/tr-select-table.html, + 'label' => 'Export', + 'table' => 'part_export', + 'name_col' => 'label', + 'hashref' => {}, + 'extra_sql' => ' WHERE exporttype IN('.join(',', @exporttypes).')', + 'disable_empty' => 1, + 'order_by' => 'ORDER BY exportnum', +&> + +<& /elements/tr-input-beginning_ending.html &> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<& /elements/footer.html &> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Usage: RADIUS sessions'); + # yes? + +my $title = 'Data Usage Report'; + +</%init> diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html index 14c284fb7..74bf5538e 100755 --- a/httemplate/search/report_svc_acct.html +++ b/httemplate/search/report_svc_acct.html @@ -114,7 +114,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #? + unless $FS::CurrentUser::CurrentUser->access_right('Services: Accounts: Advanced search'); #? my $title = emt('Account Report'); diff --git a/httemplate/search/report_svc_broadband.html b/httemplate/search/report_svc_broadband.html index 37f21b79f..d7422eeae 100755 --- a/httemplate/search/report_svc_broadband.html +++ b/httemplate/search/report_svc_broadband.html @@ -76,7 +76,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #? + unless $FS::CurrentUser::CurrentUser->access_right('Services: Wireless broadband services: Advanced search'); my $title = 'Broadband Service Report'; my $routernum = [ $cgi->param('routernum') || '' ]; diff --git a/httemplate/search/report_svc_hardware.html b/httemplate/search/report_svc_hardware.html index 61ba4ab8a..b0bfc08c7 100755 --- a/httemplate/search/report_svc_hardware.html +++ b/httemplate/search/report_svc_hardware.html @@ -61,7 +61,7 @@ OR (SELECT COUNT(*) FROM svc_hardware <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('List packages'); #? + unless $FS::CurrentUser::CurrentUser->access_right('Services: Hardware: Advanced search'); my $title = 'Hardware Service Report'; diff --git a/httemplate/search/report_tax-xls.cgi b/httemplate/search/report_tax-xls.cgi index 1c278dfd1..f19f85aaa 100755 --- a/httemplate/search/report_tax-xls.cgi +++ b/httemplate/search/report_tax-xls.cgi @@ -1,9 +1,25 @@ <% $data %> <%init> +my $htmldoc = include('report_tax.cgi'); + +my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i); + +# do this first so we can override the format if it's too many rows +# attribs option: how to locate the table? It's the only one with class="grid". +my $te = HTML::TableExtract->new(attribs => {class => 'grid'}); +$te->parse($htmldoc); +my $table = $te->first_table_found; + +my $override = ($table->row_count >= 65536 ? 'XLSX' : ''); +my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format($override); +my $filename = 'report_tax'.$format->{extension}; + +http_header('Content-Type' => $format->{mime_type}); +http_header('Content-Disposition' => qq!attachment;filename="$filename"! ); my $data = ''; my $XLS = new IO::Scalar \$data; -my $workbook = Spreadsheet::WriteExcel->new($XLS) +my $workbook = $format->{class}->new($XLS) or die "Error opening .xls file: $!"; # hardcoded formats, this could be handled better @@ -66,15 +82,6 @@ foreach (keys(%format)) { } my $ws = $workbook->add_worksheet('taxreport'); -my $htmldoc = include('report_tax.cgi'); - -my ($title) = ($htmldoc =~ /<title>\s*(.*)\s*<\/title>/i); - -# attribs option: how to locate the table? It's the only one with class="grid". -my $te = HTML::TableExtract->new(attribs => {class => 'grid'}); -$te->parse($htmldoc); -my $table = $te->first_table_found; - my @sheet; $sheet[0][0] = { text => $title, @@ -148,6 +155,4 @@ for my $x (0..scalar(@widths)-1) { $workbook->close; -http_header('Content-Type' => 'application/vnd.ms-excel'); -http_header('Content-Disposition' => 'attachment;filename="report_tax.xls"'); </%init> diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 0cd652d83..42a52d154 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -60,9 +60,9 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea % my $link = ''; % if ( $region->{'label'} eq $out ) { % $link = ';out=1'; -% } else { -% $link = ';'. $region->{'url_param'} -% if $region->{'url_param'}; +% } elsif ( $region->{'taxnums'} ) { +% # might be nicer to specify this as country:state:city +% $link = ';'.join(';', map { "taxnum=$_" } @{ $region->{'taxnums'} }); % } % % if ( $bgcolor eq $bgcolor1 ) { @@ -71,15 +71,12 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea % $bgcolor = $bgcolor1; % } % -% #my $diff = 0; % my $hicolor = $bgcolor; % unless ( $cgi->param('show_taxclasses') ) { % my $diff = abs( sprintf( '%.2f', $region->{'owed'} ) % - sprintf( '%.2f', $region->{'tax'} ) % ); % if ( $diff > 0.02 ) { -% # $hicolor = $hicolor eq '#eeeeee' ? '#eeee66' : '#ffff99'; -% #} elsif ( $diff ) { % $hicolor = $hicolor eq '#eeeeee' ? '#eeee99' : '#ffffcc'; % } % } @@ -94,16 +91,19 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea <<%$td%>><% $region->{'label'} %></TD> <<%$td%> ALIGN="right"> <A HREF="<% $baselink. $link %>;nottax=1" - ><% &$money_sprintf( $region->{'total'} ) %></A> + ><% &$money_sprintf( $region->{'sales'} ) %></A> </TD> +% if ( $region->{'label'} eq $out ) { + <<%$td%> COLSPAN=12></TD> +% } else { #not $out <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD> <<%$td%> ALIGN="right"> - <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y" + <A HREF="<% $baselink. $link %>;nottax=1;exempt_cust=Y" ><% &$money_sprintf( $region->{'exempt_cust'} ) %></A> </TD> <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD> <<%$td%> ALIGN="right"> - <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y" + <A HREF="<% $baselink. $link %>;nottax=1;exempt_pkg=Y" ><% &$money_sprintf( $region->{'exempt_pkg'} ) %></A> </TD> <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD> @@ -122,12 +122,24 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea <<%$tdh%> ALIGN="right"> <% &$money_sprintf( $region->{'owed'} ) %> </TD> - -% unless ( $cgi->param('show_taxclasses') ) { +% } # if !$out +% unless ( $cgi->param('show_taxclasses') ) { % my $invlink = $region->{'url_param_inv'} % ? ';'. $region->{'url_param_inv'} % : $link; +% if ( $region->{'label'} eq $out ) { + <<%$td%> ALIGN="right"> + <A HREF="<% $baselink. $invlink %>;istax=1" + ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A> + </TD> + <<%$td%>></TD> + <<%$td%> ALIGN="right"> + <A HREF="<% $creditlink. $invlink %>;istax=1" + ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A> + </TD> + <<%$td%> COLSPAN=2></TD> +% } else { #not $out <<%$tdh%> ALIGN="right"> <A HREF="<% $baselink. $invlink %>;istax=1" ><% &$money_sprintf( $region->{'tax'} ) %></A> @@ -141,7 +153,8 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea <<%$tdh%> ALIGN="right"> <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %> </TD> -% } +% } +% } # not $out </TR> % } @@ -190,6 +203,18 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea <TR> <<%$td%>><% $region->{'label'} %></TD> +% if ( $region->{'label'} eq $out ) { + <<%$td%> ALIGN="right"> + <A HREF="<% $baselink. $invlink %>;istax=1" + ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A> + </TD> + <<%$td%>></TD> + <<%$td%> ALIGN="right"> + <A HREF="<% $creditlink. $invlink %>;istax=1" + ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A> + </TD> + <<%$td%> COLSPAN=2></TD> +% } else { #not $out <<%$td%> ALIGN="right"> <A HREF="<% $baselink. $link %>;istax=1" ><% &$money_sprintf( $region->{'tax'} ) %></A> @@ -204,36 +229,12 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %> </TD> </TR> - -% } - -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } -% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor"); - - <TR> - <<%$td%>>Total</TD> - <<%$td%> ALIGN="right"> - <A HREF="<% $baselink %>;istax=1" - ><% &$money_sprintf( $tot_tax ) %></A> - </TD> - <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD> - <<%$td%> ALIGN="right"> - <A HREF="<% $creditlink %>;istax=1" - ><% &$money_sprintf( $tot_credit ) %></A> - </TD> - <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD> - <<%$td%> ALIGN="right"> - <% &$money_sprintf( $tot_tax - $tot_credit ) %> - </TD> - </TR> +% } # if $out +% } #foreach $region </TABLE> -% } +% } # if show_taxclasses <% include('/elements/footer.html') %> @@ -242,26 +243,38 @@ as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel sprea die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); +my $DEBUG = $cgi->param('debug') || 0; + my $conf = new FS::Conf; -my $user = getotaker; +my $out = 'Out of taxable region(s)'; + +my %label_opt = ( out => 1 ); #enable 'Out of Taxable Region' label +$label_opt{no_city} = 1 unless $cgi->param('show_cities'); +$label_opt{no_taxclass} = 1 unless $cgi->param('show_taxclasses'); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); my $join_cust = ' JOIN cust_bill USING ( invnum ) LEFT JOIN cust_main USING ( custnum ) '; + my $join_cust_pkg = $join_cust. ' LEFT JOIN cust_pkg USING ( pkgnum ) LEFT JOIN part_pkg USING ( pkgpart ) '; -$join_cust_pkg .= ' LEFT JOIN cust_location USING ( locationnum )' - if $conf->exists('tax-pkg_address'); my $from_join_cust_pkg = " FROM cust_bill_pkg $join_cust_pkg "; -my $where = "WHERE _date >= $beginning AND _date <= $ending "; +# either or both of these can be used to link cust_bill_pkg to cust_main_county +my $pkg_tax = "SELECT SUM(amount) as tax_amount, invnum, taxnum, ". + "cust_bill_pkg_tax_location.pkgnum ". + "FROM cust_bill_pkg_tax_location JOIN cust_bill_pkg USING (billpkgnum) ". + "GROUP BY billpkgnum, invnum, taxnum, cust_bill_pkg_tax_location.pkgnum"; -my( $location_sql, @base_param ) = FS::cust_pkg->location_sql; -$where .= " AND $location_sql "; +my $pkg_tax_exempt = "SELECT SUM(amount) AS exempt_charged, billpkgnum, taxnum ". + "FROM cust_tax_exempt_pkg EXEMPT_WHERE GROUP BY billpkgnum, taxnum"; + +my $where = "WHERE _date >= $beginning AND _date <= $ending "; +my $group = "GROUP BY cust_main_county.taxnum"; my $agentname = ''; if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { @@ -271,304 +284,188 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { $where .= ' AND cust_main.agentnum = '. $agent->agentnum; } -sub gotcust { - my $table = shift; - my $prefix = @_ ? shift : ''; - " - ( $table.${prefix}city = cust_main_county.city - OR cust_main_county.city = '' - OR cust_main_county.city IS NULL ) - AND ( $table.${prefix}county = cust_main_county.county - OR cust_main_county.county = '' - OR cust_main_county.county IS NULL ) - AND ( $table.${prefix}state = cust_main_county.state - OR cust_main_county.state = '' - OR cust_main_county.state IS NULL ) - AND ( $table.${prefix}country = cust_main_county.country ) - "; -} - -my $gotcust; -if ( $conf->exists('tax-ship_address') ) { - - $gotcust = " - ( cust_main_county.country = cust_main.country - OR cust_main_county.country = cust_main.ship_country - ) - - AND - - ( - ( ( ship_last IS NULL OR ship_last = '' ) - AND ". gotcust('cust_main'). " - ) - OR - ( ship_last IS NOT NULL AND ship_last != '' - AND ". gotcust('cust_main', 'ship_'). " - ) - ) - "; - -} else { - - $gotcust = gotcust('cust_main'); - +my $nottax = 'cust_bill_pkg.pkgnum != 0'; + +# one query for each column of the report +# plus separate queries for the totals row +my (%sql, %all_sql); + +# general form +my $exempt = "SELECT cust_main_county.taxnum, SUM(exempt_charged) + FROM cust_main_county + JOIN ($pkg_tax_exempt) AS pkg_tax_exempt + USING (taxnum) + JOIN cust_bill_pkg USING (billpkgnum) + $join_cust $where AND $nottax $group"; + +my $all_exempt = "SELECT SUM(exempt_charged) + FROM cust_main_county + JOIN ($pkg_tax_exempt) AS pkg_tax_exempt + USING (taxnum) + JOIN cust_bill_pkg USING (billpkgnum) + $join_cust $where AND $nottax"; + +# sales to tax-exempt customers +$sql{exempt_cust} = $exempt; +$sql{exempt_cust} =~ s/EXEMPT_WHERE/WHERE exempt_cust = 'Y' OR exempt_cust_taxname = 'Y'/; +$all_sql{exempt_cust} = $all_exempt; +$all_sql{exempt_cust} =~ s/EXEMPT_WHERE/WHERE exempt_cust = 'Y' OR exempt_cust_taxname = 'Y'/; + +# sales of tax-exempt packages +$sql{exempt_pkg} = $exempt; +$sql{exempt_pkg} =~ s/EXEMPT_WHERE/WHERE exempt_setup = 'Y' OR exempt_recur = 'Y'/; +$all_sql{exempt_pkg} = $all_exempt; +$all_sql{exempt_pkg} =~ s/EXEMPT_WHERE/WHERE exempt_setup = 'Y' OR exempt_recur = 'Y'/; + +# monthly per-customer exemptions +$sql{exempt_monthly} = $exempt; +$sql{exempt_monthly} =~ s/EXEMPT_WHERE/WHERE exempt_monthly = 'Y'/; +$all_sql{exempt_monthly} = $all_exempt; +$all_sql{exempt_monthly} =~ s/EXEMPT_WHERE/WHERE exempt_monthly = 'Y'/; + +# taxable sales +$sql{taxable} = "SELECT cust_main_county.taxnum, + SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(exempt_charged, 0)) + FROM cust_main_county + JOIN ($pkg_tax) AS pkg_tax USING (taxnum) + JOIN cust_bill_pkg USING (invnum, pkgnum) + LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt + ON (pkg_tax_exempt.billpkgnum = cust_bill_pkg.billpkgnum + AND pkg_tax_exempt.taxnum = cust_main_county.taxnum) + $join_cust $where AND $nottax $group"; + +# Here we're going to sum all line items that are taxable _at all_, +# under any tax. exempt_charged is the sum of all exemptions for a +# particular billpkgnum + taxnum; we take the taxnum that has the +# smallest sum of exemptions and subtract that from the charged amount. +$all_sql{taxable} = "SELECT + SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(min_exempt, 0)) + FROM cust_bill_pkg + JOIN ( + SELECT invnum, pkgnum, MIN(exempt_charged) AS min_exempt + FROM ($pkg_tax) AS pkg_tax + JOIN cust_bill_pkg USING (invnum, pkgnum) + LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum, taxnum) + GROUP BY invnum, pkgnum + ) AS pkg_is_taxable + USING (invnum, pkgnum) + $join_cust $where AND $nottax"; + # we don't join pkg_tax_exempt.taxnum here, because + +$sql{taxable} =~ s/EXEMPT_WHERE//; # unrestricted +$all_sql{taxable} =~ s/EXEMPT_WHERE//; + +# there isn't one for 'sales', because we calculate sales by adding up +# the taxable and exempt columns. + +# sum of billed tax: +# join cust_bill_pkg to cust_main_county via cust_bill_pkg_tax_location +my $taxfrom = " FROM cust_bill_pkg + $join_cust + LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum ) + LEFT JOIN cust_main_county USING ( taxnum )"; + +my $istax = "cust_bill_pkg.pkgnum = 0"; +my $named_tax = "( + taxname = itemdesc + OR ( taxname IS NULL + AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' ) + ) +)"; + +$sql{tax} = "SELECT cust_main_county.taxnum, + SUM(cust_bill_pkg_tax_location.amount) + $taxfrom + $where AND $istax AND $named_tax + $group"; + +$all_sql{tax} = "SELECT SUM(cust_bill_pkg.setup) + FROM cust_bill_pkg + $join_cust + $where AND $istax"; + +# sum of credits applied against billed tax +my $creditfrom = $taxfrom . + ' JOIN cust_credit_bill_pkg USING (billpkgtaxlocationnum)'; +my $creditfromwhere = $where . + ' AND billpkgtaxratelocationnum IS NULL'; + +$sql{credit} = "SELECT cust_main_county.taxnum, + SUM(cust_credit_bill_pkg.amount) + $creditfrom + $creditfromwhere AND $istax AND $named_tax + $group"; + +$all_sql{credit} = "SELECT SUM(cust_credit_bill_pkg.amount) + FROM cust_credit_bill_pkg + JOIN cust_bill_pkg USING (billpkgnum) + $join_cust + $where AND $istax"; + +my %data; +my %total = (owed => 0); +foreach my $k (keys(%sql)) { + my $stmt = $sql{$k}; + warn "\n".uc($k).":\n".$stmt."\n" if $DEBUG; + my $sth = dbh->prepare($stmt); + # two columns => key/value + $sth->execute + or die "failed to execute $k query: ".$sth->errstr; + $data{$k} = +{ map { @$_ } @{ $sth->fetchall_arrayref([]) } }; + + warn "\n".$all_sql{$k}."\n" if $DEBUG; + $total{$k} = FS::Record->scalar_sql( $all_sql{$k} ); + warn Dumper($data{$k}) if $DEBUG > 1; } -if ( $conf->exists('tax-pkg_address') ) { - $gotcust = " - ( cust_pkg.locationnum IS NULL AND $gotcust) - OR ( cust_pkg.locationnum IS NOT NULL AND ". gotcust('cust_location'). " )"; - $gotcust = - "WHERE 0 < ( SELECT COUNT(*) FROM cust_pkg - LEFT JOIN cust_main USING ( custnum ) - LEFT JOIN cust_location USING ( locationnum ) - WHERE $gotcust - LIMIT 1 - ) - "; -} else { - $gotcust = - "WHERE 0 < ( SELECT COUNT(*) FROM cust_main WHERE $gotcust LIMIT 1 )"; -} - -my $out = 'Out of taxable region(s)'; -my %regions = (); - -foreach my $r ( qsearch({ 'table' => 'cust_main_county', - 'extra_sql' => $gotcust, - }) - ) -{ - #warn $r->county. ' '. $r->state. ' '. $r->country. "\n"; - - my $label = getlabel($r); - $regions{$label}->{'label'} = $label; - - $regions{$label}->{$_} = $r->$_() for (qw( county state country )); #taxname? - - my @url_param = qw( county state country taxname ); - push @url_param, 'city' if $cgi->param('show_cities') && $r->city(); - - $regions{$label}->{'url_param'} = - join(';', map "$_=".uri_escape($r->$_()), @url_param ); - - my @param = @base_param; - my $mywhere = $where; - - if ( $r->taxclass ) { - - $mywhere .= " AND taxclass = ? "; - push @param, 'taxclass'; - $regions{$label}->{'url_param'} .= ';taxclass='. uri_escape($r->taxclass); - #no, always# if $cgi->param('show_taxclasses'); - - $regions{$label}->{'taxclass'} = $r->taxclass; - - } else { - - my $same_sql = $r->sql_taxclass_sameregion; - $mywhere .= " AND $same_sql" if $same_sql; - - $regions{$label}->{'url_param'} .= ';taxclassNULL=1' - if $cgi->param('show_taxclasses') - || $same_sql; - - } - - my $fromwhere = "$from_join_cust_pkg $mywhere"; # AND payby != 'COMP' "; - -# my $label = getlabel($r); -# $regions{$label}->{'label'} = $label; - - my $nottax = 'pkgnum != 0'; - - ## calculate total for this region - - my $t_sql = - "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax"; - my $t = scalar_sql($r, \@param, $t_sql); - $regions{$label}->{'total'} += $t; - - #if ( $label eq $out ) # && $t ) { - # warn "adding $t for ". - # join('/', map $r->$_, qw( taxclass county state country ) ). "\n"; - # #warn $t_sql if $r->state eq 'FL'; - #} - - ## calculate customer-exemption for this region - -## my $taxable = $t; - -# my($taxable, $x_cust) = (0, 0); -# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } -# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { -# $taxable += scalar_sql($r, \@param, -# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" -# ); -# -# $x_cust += scalar_sql($r, \@param, -# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" -# ); -# } - - #false laziness -ish w/report_tax.cgi - my $cust_exempt; - if ( $r->taxname ) { - my $q_taxname = dbh->quote($r->taxname); - $cust_exempt = - "( tax = 'Y' - OR EXISTS ( SELECT 1 FROM cust_main_exemption - WHERE cust_main_exemption.custnum = cust_main.custnum - AND cust_main_exemption.taxname = $q_taxname - ) - ) - "; - } else { - $cust_exempt = " tax = 'Y' "; - } - - my $x_cust = scalar_sql($r, \@param, - "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) - $fromwhere AND $nottax AND $cust_exempt " - ); - - $regions{$label}->{'exempt_cust'} += $x_cust; - - ## calculate package-exemption for this region - - my $x_pkg = scalar_sql($r, \@param, - "SELECT SUM( - ( CASE WHEN part_pkg.setuptax = 'Y' - THEN cust_bill_pkg.setup - ELSE 0 - END - ) - + - ( CASE WHEN part_pkg.recurtax = 'Y' - THEN cust_bill_pkg.recur - ELSE 0 - END - ) - ) - $fromwhere - AND $nottax - AND ( - ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) - OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) - ) - AND ( tax != 'Y' OR tax IS NULL ) - " - ); - $regions{$label}->{'exempt_pkg'} += $x_pkg; - - ## calculate monthly exemption (texas tax) for this region - - # count up all the cust_tax_exempt_pkg records associated with - # the actual line items. - - my $x_monthly = scalar_sql($r, \@param, - "SELECT SUM(amount) - FROM cust_tax_exempt_pkg - JOIN cust_bill_pkg USING ( billpkgnum ) - $join_cust_pkg - $mywhere" - ); - $regions{$label}->{'exempt_monthly'} += $x_monthly; - - my $taxable = $t - $x_cust - $x_pkg - $x_monthly; - $regions{$label}->{'taxable'} += $taxable; - - $regions{$label}->{'owed'} += $taxable * ($r->tax/100); - - if ( defined($regions{$label}->{'rate'}) - && $regions{$label}->{'rate'} != $r->tax.'%' ) { - $regions{$label}->{'rate'} = 'variable'; - } else { - $regions{$label}->{'rate'} = $r->tax.'%'; - } - -} - -my $distinct = "country, state, county, city, - CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname"; -my $taxclass_distinct = - #a little bit unsure of this part... test? - #ah, it looks like it winds up being irrelevant as ->{'tax'} - # from $regions is not displayed when show_taxclasses is on - ( $cgi->param('show_taxclasses') - ? " CASE WHEN taxclass IS NULL THEN '' ELSE taxclass END " - : " '' " - )." AS taxclass"; - - -my %qsearch = ( - 'select' => "DISTINCT $distinct, $taxclass_distinct", - 'table' => 'cust_main_county', - 'hashref' => {}, - 'extra_sql' => $gotcust, +# so $data{tax}, for example, is now a hash with one entry +# for each taxnum, containing the tax billed on that taxnum. + +# oddball cases: +# "out of taxable region" sales +my %out; +my $out_sales_sql = + "SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur) + FROM (cust_bill_pkg $join_cust) + LEFT JOIN ($pkg_tax) AS pkg_tax USING (invnum, pkgnum) + LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum) + $where AND $nottax + AND pkg_tax.taxnum IS NULL AND pkg_tax_exempt.taxnum IS NULL" +; + +$out_sales_sql =~ s/EXEMPT_WHERE//; + +$out{sales} = FS::Record->scalar_sql($out_sales_sql); + +# unlinked tax collected (for diagnostics) +my $out_tax_sql = + "SELECT SUM(cust_bill_pkg.setup) + FROM (cust_bill_pkg $join_cust) + LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum) + $where AND $istax AND cust_bill_pkg_tax_location.billpkgnum IS NULL" +; +$out{tax} = FS::Record->scalar_sql($out_tax_sql); +# unlinked tax credited (for diagnostics) +my $out_credit_sql = + "SELECT SUM(cust_credit_bill_pkg.amount) + FROM cust_credit_bill_pkg + JOIN cust_bill_pkg USING (billpkgnum) + $join_cust + $where AND $istax AND cust_credit_bill_pkg.billpkgtaxlocationnum IS NULL" +; +$out{credit} = FS::Record->scalar_sql($out_credit_sql); + +# all sales +$total{sales} = FS::Record->scalar_sql( + "SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur) + FROM cust_bill_pkg $join_cust $where AND $nottax" ); -my $taxfromwhere = " FROM cust_bill_pkg $join_cust "; -my $taxwhere = $where; -if ( $conf->exists('tax-pkg_address') ) { - - $taxfromwhere .= 'LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum ) - LEFT JOIN cust_location USING ( locationnum ) '; - - #quelle kludge - $taxwhere =~ s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g; - -} -my $creditfromwhere = $taxfromwhere. - " JOIN cust_credit_bill_pkg USING (billpkgnum"; -$creditfromwhere .= " ,billpkgtaxlocationnum" - if $conf->exists('tax-pkg_address'); -$creditfromwhere .= ")"; - -$taxfromwhere .= " $taxwhere "; #AND payby != 'COMP' "; -$creditfromwhere .= " $taxwhere AND billpkgtaxratelocationnum IS NULL"; #AND payby != 'COMP' "; - -my @taxparam = @base_param; - - -#should i be a cust_main_county method or something -#need to pass in $taxfromwhere & @taxparam??? -my $_taxamount_sub = sub { - my $r = shift; - - #match itemdesc if necessary! - my $named_tax = - $r->taxname - ? 'AND itemdesc = '. dbh->quote($r->taxname) - : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; - - my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ". - " $taxfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax"; - - scalar_sql($r, \@taxparam, $sql ); -}; - -my $_creditamount_sub = sub { - my $r = shift; - - #match itemdesc if necessary! - my $named_tax = - $r->taxname - ? 'AND itemdesc = '. dbh->quote($r->taxname) - : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; - - my $sql = "SELECT SUM(cust_credit_bill_pkg.amount) ". - " $creditfromwhere AND cust_bill_pkg.pkgnum = 0 $named_tax"; - - scalar_sql($r, \@taxparam, $sql ); -}; - #tax-report_groups filtering my($group_op, $group_value) = ( '', '' ); if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) { ( $group_op, $group_value ) = ( $1, $2 ); } -my $group_test = sub { +my $group_test = sub { # to be applied to a tax label my $label = shift; return 1 unless $group_op; #in case we get called inadvertantly if ( $label eq $out ) { #don't display "out of taxable region" in this case @@ -582,86 +479,83 @@ my $group_test = sub { } }; +# if show_taxclasses is on, %base_regions will contain the same data +# as %regions, but with taxclasses merged together (and ignoring report_group +# filtering). +my (%regions, %base_regions); my $tot_tax = 0; my $tot_credit = 0; -#foreach my $label ( keys %regions ) { -foreach my $r ( qsearch(\%qsearch) ) { - - #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n"; - my $label = getlabel($r); - if ( $group_op ) { - next unless &{$group_test}($label); +my @loc_params = qw(country state county); +push @loc_params, qw(city district) if $cgi->param('show_cities'); + +foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) { + my $taxnum = $r->taxnum; + # set up a %regions entry for this region's tax label + my $label = $r->label(%label_opt); + next if $label eq $out; + $regions{$label} ||= { label => $label }; + + $regions{$label}->{$_} = $r->get($_) foreach @loc_params; + $regions{$label}->{taxnums} ||= []; + push @{ $regions{$label}->{taxnums} }, $r->taxnum; + + my %x; # keys are data items (like 'tax', 'exempt_cust', etc.) + foreach my $k (keys %data) { + next unless exists($data{$k}->{$taxnum}); + $x{$k} = $data{$k}->{$taxnum}; + $regions{$label}->{$k} += $x{$k}; + if ( $k eq 'taxable' or $k =~ /^exempt/ ) { + $regions{$label}->{'sales'} += $x{$k}; + } } - #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; - #my @param = @base_param; + my $owed = $data{'taxable'}->{$taxnum} * ($r->tax/100); + $regions{$label}->{'owed'} += $owed; + $total{'owed'} += $owed; - my $x = &{$_taxamount_sub}($r); - - $regions{$label}->{'tax'} += $x; - $tot_tax += $x unless $cgi->param('show_taxclasses'); - - ## calculate credit for this region - - $x = &{$_creditamount_sub}($r); - - $regions{$label}->{'credit'} += $x; - $tot_credit += $x unless $cgi->param('show_taxclasses'); - -} - -my %base_regions = (); -if ( $cgi->param('show_taxclasses') ) { - - $qsearch{'select'} = "DISTINCT $distinct"; - foreach my $r ( qsearch(\%qsearch) ) { - - my $x = &{$_taxamount_sub}($r); - - my $base_label = getlabel($r, 'no_taxclass'=>1 ); - $base_regions{$base_label}->{'label'} = $base_label; - - $base_regions{$base_label}->{'url_param'} = - join(';', map "$_=". uri_escape($r->$_()), - qw( county state country taxname ) - ); - - $base_regions{$base_label}->{'tax'} += $x; - $tot_tax += $x; - - ## calculate credit for this region - - $x = &{$_creditamount_sub}($r); - - $base_regions{$base_label}->{'credit'} += $x; - $tot_credit += $x; + if ( defined($regions{$label}->{'rate'}) + && $regions{$label}->{'rate'} != $r->tax.'%' ) { + $regions{$label}->{'rate'} = 'variable'; + } else { + $regions{$label}->{'rate'} = $r->tax.'%'; + } + if ( $cgi->param('show_taxclasses') ) { + my $base_label = $r->label(%label_opt, 'no_taxclass' => 1); + $base_regions{$base_label} ||= + { + label => $base_label, + tax => 0, + credit => 0, + }; + $base_regions{$base_label}->{tax} += $x{tax}; + $base_regions{$base_label}->{credit} += $x{credit}; } } -my @regions = keys %regions; +my @regions = map { $_->{label} } + sort { + ($b eq $out) <=> ($a eq $out) + or $a->{country} cmp $b->{country} + or $a->{state} cmp $b->{state} + or $a->{county} cmp $b->{county} + or $a->{city} cmp $b->{city} + } + grep { $_->{sales} > 0 or $_->{tax} > 0 or $_->{credit} > 0 } + values %regions; #tax-report_groups filtering @regions = grep &{$group_test}($_), @regions if $group_op; #calculate totals -my( $total, $tot_taxable, $tot_owed ) = ( 0, 0, 0 ); -my( $exempt_cust, $exempt_pkg, $exempt_monthly, $tot_credit ) = ( 0, 0, 0, 0 ); my %taxclasses = (); my %county = (); my %state = (); my %country = (); -foreach (@regions) { - $total += $regions{$_}->{'total'}; - $tot_taxable += $regions{$_}->{'taxable'}; - $tot_owed += $regions{$_}->{'owed'}; - $exempt_cust += $regions{$_}->{'exempt_cust'}; - $exempt_pkg += $regions{$_}->{'exempt_pkg'}; - $exempt_monthly += $regions{$_}->{'exempt_monthly'}; - $tot_credit += $regions{$_}->{'credit'}; +foreach my $label (@regions) { $taxclasses{$regions{$_}->{'taxclass'}} = 1 if $regions{$_}->{'taxclass'}; $county{$regions{$_}->{'county'}} = 1; @@ -696,29 +590,27 @@ if ( $group_op ) { #ordering @regions = map $regions{$_}, - sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } + sort { $a cmp $b } @regions; my @base_regions = map $base_regions{$_}, - sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } + sort { $a cmp $b } keys %base_regions; -#add total line -push @regions, { - 'label' => 'Total', - 'url_param' => $total_url_param, - 'url_param_inv' => $total_url_param_invoiced, - 'total' => $total, - 'exempt_cust' => $exempt_cust, - 'exempt_pkg' => $exempt_pkg, - 'exempt_monthly' => $exempt_monthly, - 'taxable' => $tot_taxable, - 'rate' => '', - 'owed' => $tot_owed, - 'tax' => $tot_tax, - 'credit' => $tot_credit, -}; +#add "Out of taxable" and total lines +%out = ( %out, + 'label' => $out, + 'rate' => '' +); +%total = ( %total, + 'label' => 'Total', + 'url_param' => $total_url_param, + 'url_param_inv' => $total_url_param_invoiced, + 'rate' => '', +); +push @regions, \%out, \%total; +push @base_regions, \%out, \%total; #-- @@ -726,72 +618,15 @@ my $money_char = $conf->config('money_char') || '$'; my $money_sprintf = sub { $money_char. sprintf('%.2f', shift ); }; - -sub getlabel { - my $r = shift; - my %opt = @_; - - my $label; - if ( - $r->tax == 0 - && ! scalar( qsearch('cust_main_county', { 'city' => $r->city, - 'county' => $r->county, - 'state' => $r->state, - 'country' => $r->country, - 'tax' => { op=>'>', value=>0 }, - } - ) - ) - - ) { - #kludge to avoid "will not stay shared" warning - my $out = 'Out of taxable region(s)'; - $label = $out; -# } elsif ( $r->taxname && count_taxname($r->taxname) == 1 ) { -# $label = $r->taxname; -## $regions{$label}->{'taxname'} = $label; -## push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country ); - } else { - $label = $r->country; - $label = $r->state.", $label" if $r->state; - $label = $r->county." county, $label" if $r->county; - $label = $r->city. ", $label" if $r->city && $cgi->param('show_cities'); - $label = "$label (". $r->taxclass. ")" - if $r->taxclass - && $cgi->param('show_taxclasses') - && ! $opt{'no_taxclass'}; - $label = $r->taxname. " ($label)" if $r->taxname; - } - return $label; -} - -#my %count_taxname = (); #cache -#sub count_taxname { -# my $taxname = shift; -# return $count_taxname{$taxname} if exists $count_taxname{$taxname}; -# my $sql = 'SELECT COUNT(*) FROM cust_main_county WHERE taxname = ?'; -# my $sth = dbh->prepare($sql) or die dbh->errstr; -# $sth->execute( $taxname ) -# or die "Unexpected error executing statement $sql: ". $sth->errstr; -# $count_taxname{$taxname} = $sth->fetchrow_arrayref->[0]; -#} - -#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up -#to FS::Report or FS::Record or who the fuck knows where) -sub scalar_sql { - my( $r, $param, $sql ) = @_; - #warn "$sql\n"; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute( map $r->$_(), @$param ) - or die "Unexpected error executing statement $sql: ". $sth->errstr; - $sth->fetchrow_arrayref->[0] || 0; -} +my $money_sprintf_nonzero = sub { + $_[0] == 0 ? '' : &$money_sprintf($_[0]) +}; my $dateagentlink = "begin=$beginning;end=$ending"; $dateagentlink .= ';agentnum='. $cgi->param('agentnum') if length($agentname); my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink"; my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; -my $creditlink = $p. "search/cust_credit_bill_pkg.html?$dateagentlink"; +my $creditlink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;credit=1"; </%init> diff --git a/httemplate/search/sqlradius_usage.html b/httemplate/search/sqlradius_usage.html new file mode 100644 index 000000000..29ef4c0e8 --- /dev/null +++ b/httemplate/search/sqlradius_usage.html @@ -0,0 +1,201 @@ +% if ( @include_agents ) { +% # jumbo report +<& /elements/header.html, $title &> +% foreach my $agent ( @include_agents ) { +% $cgi->param('agentnum', $agent->agentnum); #for download links +<DIV WIDTH="100%" STYLE="page-break-after: always"> +<FONT SIZE=6><% $agent->agent %></FONT><BR><BR> + <& sqlradius_usage.html, + export => $export, + agentnum => $agent->agentnum, + nohtmlheader => 1, + usage_by_username => \%usage_by_username, + download_label => 'Download this section', + &> +</DIV> +<BR><BR> +% } +<& /elements/footer.html &> +% } else { +<& elements/search.html, + 'title' => $title, + 'name' => 'services', + 'query' => $sql_query, + 'count_query' => $sql_query->{'count_query'}, + 'header' => [ #FS::UI::Web::cust_header(), + '#', + 'Customer', + 'Package', + @svc_header, + 'Upload (GB)', + 'Download (GB)', + 'Total (GB)', + ], + 'footer' => \@footer, + 'fields' => [ #\&FS::UI::Web::cust_fields, + 'display_custnum', + 'name', + 'pkg', + @svc_fields, + @svc_usage, + ], + 'links' => [ #( map { $_ ne 'Cust. Status' ? $link_cust : '' } + # FS::UI::Web::cust_header() ), + $link_cust, + $link_cust, + '', #package + ( map { $link_svc } @svc_header ), + '', + '', + '', + ], + 'align' => #FS::UI::Web::cust_aligns() . + 'rlc' . ('l' x scalar(@svc_header)) . 'rrr' , + 'nohtmlheader' => ($opt{'nohtmlheader'} || 0), + 'download_label' => $opt{'download_label'}, +&> +% } +<%init> + +my %opt = @_; + +die "access denied" unless + $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $title = 'Data Usage Report - '; +my $agentnum; +my @include_agents; + +if ( $opt{'agentnum'} ) { + $agentnum = $opt{'agentnum'}; +} elsif ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; +} + +if ( $agentnum ) { + my $agent = FS::agent->by_key($agentnum); + $title = $agent->agent." $title"; +} else { + @include_agents = qsearch('agent', {}); +} + +# usage query params +my( $beginning, $ending ) = FS::UI::Web::parse_beginning_ending($cgi); + +if ( $beginning ) { + $title .= time2str('%h %o %Y ', $beginning); +} +$title .= 'through '; +if ( $ending == 4294967295 ) { + $title .= 'now'; +} else { + $title .= time2str('%h %o %Y', $ending); +} + +my $export; +my %usage_by_username; +if ( exists($opt{usage_by_username}) ) { + # There's no agent separation in the radacct data. So in the jumbo report + # do this procedure once, and pass the hash into all the per-agent sections. + %usage_by_username = %{ $opt{usage_by_username} }; + $export = $opt{export}; +} else { + + $cgi->param('exportnum') =~ /^(\d+)$/ + or die "illegal export: '".$cgi->param('exportnum')."'"; + $export = FS::part_export->by_key($1) + or die "exportnum $1 not found"; + $export->exporttype =~ /sqlradius/ + or die "exportnum ".$export->exportnum." is type ".$export->exporttype. + ", not sqlradius"; + + my $usage = $export->usage_sessions( { + stoptime_start => $beginning, + stoptime_end => $ending, + summarize => 1 + } ); + # arrayref of hashrefs of + # (username, acctsessiontime, acctinputoctets, acctoutputoctets) + # (XXX needs to include 'realm' for sqlradius_withdomain) + # rearrange to be indexed by username. + + foreach (@$usage) { + my $username = $_->{'username'}; + my @row = ( + $_->{'acctinputoctets'}, + $_->{'acctoutputoctets'}, + $_->{'acctinputoctets'} + $_->{'acctoutputoctets'} + ); + $usage_by_username{$username} = \@row; + } +} + +#warn Dumper(\%usage_by_username); +my @total_usage = (0, 0, 0, 0); # session time, input, output, input + output +my @svc_usage = map { + my $i = $_; + sub { + my $username = $export->export_username(shift); + return '' if !exists($usage_by_username{$username}); + my $value = $usage_by_username{ $username }->[$i]; + $total_usage[$i] += $value; + # for now, always show in GB, rounded to 3 digits + bytes_to_gb($value); + } +} (0,1,2); + +# set up svcdb-specific stuff +my $export_username = sub { + $export->export_username(shift); # countrycode + phone, formatted MAC, etc. +}; + +my %svc_header = ( + svc_acct => [ 'Username' ], + svc_broadband => [ 'MAC address', 'IP address' ], +# svc_phone => [ 'Phone' ], #not yet supported, no search method + # (not sure input/output octets is relevant) +); +my %svc_fields = ( + svc_acct => [ $export_username ], + svc_broadband => [ $export_username, 'ip_addr' ], +# svc_phone => [ $export_username ], +); + +# what kind of service we're operating on +my $svcdb = FS::part_export::export_info()->{$export->exporttype}->{'svc'}; +my $class = "FS::$svcdb"; +my @svc_header = @{ $svc_header{$svcdb} }; +my @svc_fields = @{ $svc_fields{$svcdb} }; + +# svc_x search params +my %search_hash = ( 'agentnum' => $agentnum, + 'exportnum' => $export->exportnum ); + +my $sql_query = $class->search(\%search_hash); +$sql_query->{'select'} .= ', part_pkg.pkg'; +$sql_query->{'addl_from'} .= ' LEFT JOIN part_pkg USING (pkgpart)'; + +my $link_svc = [ $p.'view/cust_svc.cgi?', 'svcnum' ]; + +my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ]; + +# columns between the customer name and the usage fields +my $skip_cols = 1 + scalar(@svc_header); + +my @footer = ( + '', + FS::Record->scalar_sql($sql_query->{count_query}) . ' services', + ('') x $skip_cols, + map { + my $i = $_; + sub { # defer this until the rows have been processed + bytes_to_gb($total_usage[$i]) + } + } (0,1,2) +); + +sub bytes_to_gb { + $_[0] ? sprintf('%.3f', $_[0] / (1024*1024*1024.0)) : ''; +} + +</%init> diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html new file mode 100644 index 000000000..02d514cbe --- /dev/null +++ b/httemplate/search/unearned_detail.html @@ -0,0 +1,257 @@ +<& elements/search.html, + 'title' => emt("Unearned revenue - ".ucfirst($unearned_mode)) . ' (' . + time2str('%b %d %Y', $unearned) . ')', + 'name' => emt('line items'), + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', + $money_char. '%.2f unearned revenue' + ], + 'header' => [ map( {emt $_} + 'Description', + 'Unearned', # depends on mode + 'Recurring charge', #recur - usage + 'Owed', #recur - usage - credits - payments + 'Paid', #payments + 'Payment date', #of last payment + 'Credit date', #of last credit + 'Charge start', + 'Charge end', + 'Invoice', + 'Date' + ), + FS::UI::Web::cust_header(), + ], + 'fields' => [ + #Description + sub { $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') # possibly use override.pkg + : $_[0]->get('itemdesc') # but i think this correct + }, + #Unearned + money_sub('unearned_revenue'), + #Recurring charge + money_sub('recur_no_usage'), + #Owed + money_sub('owed_no_usage'), + #Paid + money_sub('paid_no_usage'), + #Payment date + date_sub('last_pay'), + #Credit date + date_sub('last_credit'), + #Charge start + date_sub('sdate'), + #Charge end, minus most of a day + date_sub('before_edate'), + #Invoice + 'invnum', + #Invoice date + date_sub('_date'), + \&FS::UI::Web::cust_fields, + ], + 'sort_fields' => [ + 'pkg', + # SQL expressions work as sort keys... + 'unearned_revenue', + 'recur_no_usage', + 'owed_no_usage', + 'paid_no_usage', + 'last_pay', + 'last_credit', + 'sdate', + 'edate', + 'invnum', + '_date', + ], + 'links' => [ + ('' x 9), + $ilink, + $ilink, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'lrrcrccrc'. FS::UI::Web::cust_aligns(), + 'color' => [ + ('' x 11), + FS::UI::Web::cust_colors(), + ], + 'style' => [ + ('' x 11), + FS::UI::Web::cust_styles(), + ], +&> +<%init> + +# Separated from cust_bill_pkg.cgi to simplify things. + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; + +my $unearned = ''; +my $unearned_mode = ''; +my $unearned_base = ''; +my $unearned_sql = ''; + +my @select = ( 'cust_bill_pkg.*', 'cust_bill._date' ); +my ($join_cust, $join_pkg ) = ('', ''); + +#here is the agent virtualization +my $agentnums_sql = + $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' ); + +my @where = ( $agentnums_sql ); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +if ( $cgi->param('status') =~ /^([a-z]+)$/ ) { + push @where, FS::cust_main->cust_status_sql . " = '$1'"; +} + +push @where, "cust_bill._date >= $beginning", + "cust_bill._date <= $ending"; + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.agentnum = $1"; +} + +# no pkgclass, no taxclass, no tax location... + +# unearned revenue mode +$cgi->param('date') =~ /^(\d+)$/ + or die "date required"; + +$unearned = $1; +$unearned_mode = $cgi->param('mode'); + +push @where, "cust_bill_pkg.sdate < $unearned", + "cust_bill_pkg.edate > $unearned", + "cust_bill_pkg.recur != 0", + "part_pkg.freq != '0'"; + +if ( !$cgi->param('include_monthly') ) { + push @where, + "part_pkg.freq != '1'", + "part_pkg.freq NOT LIKE '%h'", + "part_pkg.freq NOT LIKE '%d'", + "part_pkg.freq NOT LIKE '%w'"; +} + +my @opt = ( + $unearned, #before this date + '', #after this date + setuprecur => 'recur', + no_usage => 1 +); + +my $charged = FS::cust_bill_pkg->charged_sql(@opt); +push @select, "($charged) AS recur_no_usage"; + +my $owed_sql = FS::cust_bill_pkg->owed_sql(@opt); +push @select, "($owed_sql) AS owed_no_usage"; + +my $paid_sql = FS::cust_bill_pkg->paid_sql(@opt); +push @select, "$paid_sql AS paid_no_usage"; + +if ( $unearned_mode eq 'paid' ) { + # then use the amount paid, minus usage charges + $unearned_base = $paid_sql; +} +else { + # use the amount billed, minus usage charges and credits + $unearned_base = "( $charged - " . + FS::cust_bill_pkg->credited_sql(@opt) . ' )'; +} +# whatever we're using as the base, only show rows where it's positive +push @where, "$unearned_base > 0"; + +my $edate_zero = midnight_sql('edate'); +my $sdate_zero = midnight_sql('sdate'); +# $unearned is one second before midnight on the date requested for the report. + +# suppress partial days for more accounting-like behavior +my $period = "CAST( ($edate_zero - $sdate_zero) / 86400.0 AS DECIMAL(10,0) )"; + +my $remaining = "GREATEST( + CAST( ($edate_zero - $unearned) / 86400.0 AS DECIMAL(10,0) ), + 0)"; +my $fraction = "$remaining / $period"; + +$unearned_sql = "CAST( $unearned_base * $fraction AS DECIMAL(10,2) )"; +push @select, "$unearned_sql AS unearned_revenue"; + +# last payment/credit date +my %t = (pay => 'cust_bill_pay', credit => 'cust_credit_bill'); +foreach my $x (qw(pay credit)) { + my $table = $t{$x}; + my $link = $table.'_pkg'; + my $pkey = dbdef->table($table)->primary_key; + my $last_date_sql = "SELECT MAX(_date) + FROM $table JOIN $link USING ($pkey) + WHERE $link.billpkgnum = cust_bill_pkg.billpkgnum + AND $table._date <= $unearned"; + push @select, "($last_date_sql) AS last_$x"; +} + +push @select, '(edate - 82799) AS before_edate'; + +#no itemdesc +#no tax report group kludge +#no tax exemption +#usage always excluded + +# always 'nottax', not 'istax' +$join_cust = ' JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) '; + +$join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + LEFT JOIN part_pkg AS override + ON pkgpart_override = override.pkgpart '; + +my $where = ' WHERE '. join(' AND ', @where); + +my $count_query = "SELECT COUNT(DISTINCT billpkgnum), + SUM( $unearned_base ), SUM( $unearned_sql ) + FROM cust_bill_pkg $join_cust $join_pkg $where"; + +push @select, 'part_pkg.pkg', + 'part_pkg.freq', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(); + +my $query = { + 'table' => 'cust_bill_pkg', + 'addl_from' => "$join_cust $join_pkg", + 'hashref' => {}, + 'select' => join(",\n", @select ), + 'extra_sql' => $where, + 'order_by' => 'ORDER BY cust_bill._date, billpkgnum', +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $money_char; + +sub money_sub { + $conf ||= new FS::Conf; + $money_char ||= $conf->config('money_char') || '$'; + my $field = shift; + sub { + $money_char . sprintf('%.2f', $_[0]->get($field)); + }; +} + +sub date_sub { + my $field = shift; + sub { + my $value = $_[0]->get($field); + $value ? time2str('%b %d %Y', $value) : ''; + }; +}; + +</%init> diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index a8b4ac15c..95ce60b1d 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -166,8 +166,6 @@ die "Invoice #$invnum not found!" unless $cust_bill; my $custnum = $cust_bill->custnum; my $display_custnum = $cust_bill->cust_main->display_custnum; -#my $printed = $cust_bill->printed; - my $link = "invnum=$invnum"; $link .= ';template='. uri_escape($template) if $template; $link .= ';notice_name='. $notice_name if $notice_name; diff --git a/httemplate/view/cust_bill_void.html b/httemplate/view/cust_bill_void.html new file mode 100755 index 000000000..2c526747b --- /dev/null +++ b/httemplate/view/cust_bill_void.html @@ -0,0 +1,79 @@ +<& /elements/header.html, mt('Voided Invoice'), menubar( + emt("View this customer (#[_1])",$display_custnum) => "${p}view/cust_main.cgi?$custnum", +) &> + +<SCRIPT TYPE="text/javascript"> +function areyousure(href, message) { + if (confirm(message) == true) + window.location.href = href; +} +</SCRIPT> +<% areyousure_link("${p}misc/unvoid-cust_bill_void.html?invnum=". $cust_bill_void->invnum, + emt('Are you sure you want to unvoid this invoice?'), + emt('Unvoid this invoice'), #tooltip + emt('Unvoid this invoice') #link + ) +%> +<BR><BR> + +% #voided PDFs? +% #if ( $conf->exists('invoice_latex') ) { +%# +%# <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>"><% mt('View typeset invoice PDF') |h %></A> +%# <BR><BR> +% #} + +%#something very big and obvious showing its voided... +<DIV STYLE="color:#FF0000; font-size:1000%; font-weight:bold; z-index:100; + position: absolute; top: 300px; left: 130px; + zoom: 1; filter: alpha(opacity=25); opacity: 0.25; +">VOID</DIV> + +% if ( $conf->exists('invoice_html') ) { + <% join('', $cust_bill_void->print_html(\%opt) ) %> +% } else { + <PRE><% join('', $cust_bill_void->print_text(\%opt) ) %></PRE> +% } + +<& /elements/footer.html &> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('View invoices'); + +my $invnum; +my($query) = $cgi->keywords; +if ( $query =~ /^(\d+)$/ ) { + $invnum = $1; +} else { + $invnum = $cgi->param('invnum'); +} + +my $conf = new FS::Conf; + +my %opt = ( + 'unsquelch_cdr' => $conf->exists('voip-cdr_email'), +); + +my $cust_bill_void = qsearchs({ + 'select' => 'cust_bill_void.*', + 'table' => 'cust_bill_void', + #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'invnum' => $invnum }, + #'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +die "Voided invoice #$invnum not found!" unless $cust_bill_void; + +my $custnum = $cust_bill_void->custnum; +my $display_custnum = $cust_bill_void->cust_main->display_custnum; + +#my $link = "invnum=$invnum"; + +sub areyousure_link { + my ($url,$msg,$title,$label) = (shift,shift,shift,shift); + '<A HREF="javascript:areyousure(\''.$url.'\',\''.$msg.'\')" TITLE="'.$title.'">'.$label.'</A>'; +} + +</%init> diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 9ce55b0b9..ec3191971 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -46,10 +46,39 @@ function areyousure(href, message) { <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>"><% mt('Edit this customer') |h %></A> | % } -% if ( $curuser->access_right('Cancel customer') -% && $cust_main->ncancelled_pkgs +% if ( $curuser->access_right('Suspend customer') +% && scalar($cust_main->unsuspended_pkgs) % ) { + <& /elements/popup_link-cust_main.html, + { 'action' => $p. 'misc/suspend_cust.html', + 'label' => emt('Suspend this customer'), + 'actionlabel' => emt('Confirm Suspension'), + 'color' => '#ff9900', + 'cust_main' => $cust_main, + 'width' => 616, #make room for reasons + 'height' => 366, + } + &> | +% } +% if ( $curuser->access_right('Unsuspend customer') +% && scalar($cust_main->suspended_pkgs) +% ) { + <& /elements/popup_link-cust_main.html, + { 'action' => $p. 'misc/unsuspend_cust.html', + 'label' => emt('Unsuspend this customer'), + 'actionlabel' => emt('Confirm Unsuspension'), + #'color' => '#ff9900', + 'cust_main' => $cust_main, + #'width' => 616, #make room for reasons + #'height' => 366, + } + &> | +% } + +% if ( $curuser->access_right('Cancel customer') +% && scalar($cust_main->ncancelled_pkgs) +% ) { <& /elements/popup_link-cust_main.html, { 'action' => $p. 'misc/cancel_cust.html', 'label' => emt('Cancel this customer'), @@ -60,11 +89,9 @@ function areyousure(href, message) { 'height' => 366, } &> | - % } % if ( $curuser->access_right('Merge customer') ) { - <& /elements/popup_link-cust_main.html, { 'action' => $p. 'misc/merge_cust.html', 'label' => emt('Merge this customer'), @@ -74,7 +101,6 @@ function areyousure(href, message) { 'height' => 192, } &> | - % } % if ( $conf->exists('deletecustomers') @@ -277,7 +303,13 @@ function areyousure(href, message) { % } % if ( $view eq 'custom' ) { +% if ( $conf->config('cust_main-custom_link') ) { <& cust_main/custom.html, $cust_main &> +% } elsif ( $conf->config('cust_main-custom_content') ) { + <& cust_main/custom_content.html, $cust_main &> +% #} else { +% # warn "custom view without cust_main-custom_link or -custom_content?"; +% } % } </DIV> @@ -326,7 +358,8 @@ $views{emt('Payment History')} = 'payment_history' $views{emt('Change History')} = 'change_history' if $curuser->access_right('View customer history'); $views{$conf->config('cust_main-custom_title') || emt('Custom')} = 'custom' - if $conf->config('cust_main-custom_link'); + if $conf->config('cust_main-custom_link') + || $conf->config('cust_main-custom_content'); $views{emt('Jumbo')} = 'jumbo'; my %viewname = reverse %views; diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index 522c6db86..5c46803d2 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -23,6 +23,14 @@ <TD BGCOLOR="#ffffff"><B><% $balance %></B></TD> </TR> +% if ( $conf->exists('cust_main-select-prorate_day') ) { +<TR> + <TD ALIGN="right"><% mt('Prorate day of month') |h %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->prorate_day %> + </TD> +</TR> +% } + % if ( $conf->exists('cust_main-select-billday') % && ($cust_main->payby eq 'CARD' || $cust_main->payby eq 'CHEK') ) { <TR> @@ -146,7 +154,7 @@ % if ( $cust_main->payinfo ) { <TR> - <TD ALIGN="right"<% mt('P.O.') |h %></TD> + <TD ALIGN="right"><% mt('P.O.') |h %></TD> <TD BGCOLOR="#ffffff"><% $cust_main->payinfo %></TD> </TR> % } @@ -189,15 +197,25 @@ % my $no = emt('no'); % my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); -<TR> - <TD ALIGN="right"><% mt('Tax exempt') |h %><% @exempt_groups ? ' ('.emt('all taxes').')' : '' %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->tax ? $yes : $no %></TD> -</TR> + +% unless ( $conf->exists('cust_class-tax_exempt') +% || $conf->exists('tax-cust_exempt-groups-require_individual_nums') +% ) +% { + <TR> + <TD ALIGN="right"><% mt('Tax exempt') |h %><% @exempt_groups ? ' ('.emt('all taxes').')' : '' %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->tax ? $yes : $no %></TD> + </TR> +% } + % foreach my $exempt_group ( @exempt_groups ) { -<TR> - <TD ALIGN="right"><% mt('Tax exempt') |h %> (<% $exempt_group %> taxes)</TD> - <TD BGCOLOR="#ffffff"><% $cust_main->tax_exemption($exempt_group) ? $yes : $no %></TD> -</TR> +% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group); + <TR> + <TD ALIGN="right"><% mt('Tax exempt') |h %> (<% $exempt_group %> taxes)</TD> + <TD BGCOLOR="#ffffff"><% $cust_main_exemption ? $yes : $no %> + <% $cust_main_exemption ? $cust_main_exemption->exempt_number : '' |h %> + </TD> + </TR> % } % if ( $conf->exists('enable_taxproducts') ) { @@ -207,21 +225,27 @@ </TR> % } <TR> - <TD ALIGN="right"><% mt('Postal invoices') |h %></TD> + <TD ALIGN="right"><% mt('Postal mail invoices') |h %></TD> <TD BGCOLOR="#ffffff"> <% ( grep { $_ eq 'POST' } @invoicing_list ) ? $yes : $no %> </TD> </TR> <TR> - <TD ALIGN="right"><% mt('FAX invoices') |h %></TD> + <TD ALIGN="right"><% mt('Fax invoices') |h %></TD> <TD BGCOLOR="#ffffff"> <% ( grep { $_ eq 'FAX' } @invoicing_list ) ? $yes : $no %> </TD> </TR> -% unless ( $conf->exists('cust-email-high-visibility')) { <TR> <TD ALIGN="right"><% mt('Email invoices') |h %></TD> <TD BGCOLOR="#ffffff"> + <% $cust_main->invoice_noemail ? $no : $yes %> + </TD> +</TR> +% unless ( $conf->exists('cust-email-high-visibility')) { +<TR> + <TD ALIGN="right"><% mt('Email address(es)') |h %></TD> + <TD BGCOLOR="#ffffff"> <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %> </TD> </TR> diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index b3e52b556..d65af669a 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -1,122 +1,133 @@ -% my %which = ( -% '' => emt('Billing'), -% 'ship_' => emt('Service'), -% ); -% foreach my $which ( '', 'ship_' ) { -% my $pre = $cust_main->get("${which}last") ? $which : ''; - -<FONT CLASS="fsinnerbox-title"><% $which{$which} %> <% mt('address') |h %></FONT> +% my %addr_label = ('bill' => 'Billing address', 'ship' => 'Service address'); + +%# Locations (possibly break this out) +% my @which = ('bill', 'ship'); +% while (@which) { +% my $this = shift @which; +% my $method = $this.'_location'; +% my $location = $cust_main->$method; +<FONT CLASS="fsinnerbox-title"><% mt( $addr_label{$this} ) |h %> +% if ( $this eq 'ship' and +% $cust_main->bill_locationnum == $cust_main->ship_locationnum ) +% { + (<% mt('same as billing') %>) +% } +</FONT> <TABLE CLASS="fsinnerbox"> -<TR> - <TD ALIGN="right"><% mt('Contact name') |h %></TD> - <TD COLSPAN=5 BGCOLOR="#ffffff"> - <% $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first") |h %> - </TD> -% if ( $which eq '' && $conf->exists('show_ss') ) { - <TD ALIGN="right"><% mt('SS#') |h %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->masked('ss') || ' ' %></TD> -% } -</TR> -% if ( $conf->exists('cust-email-high-visibility') && $which eq '') { +% if ( $this eq 'bill' ) { +% #billing contact fields + <TR> + <TD ALIGN="right"><% mt('Contact name') |h %></TD> + <TD COLSPAN=5 BGCOLOR="#ffffff"><% $cust_main->contact |h %></TD> +% if ( $conf->exists('show_ss') ) { + <TD ALIGN="right"><% mt('SS#') |h %></TD> + <TD BGCOLOR="#ffffff"><% $conf->exists('unmask_ss') + ? $cust_main->ss + : $cust_main->masked('ss') || ' ' %></TD> +% } + </TR> +% if ( $conf->exists('cust-email-high-visibility') ) { <TR> - <TD ALIGN="right"><% mt('Email invoices') |h %></TD> + <TD ALIGN="right"><% mt('Email address(es)') |h %></TD> <TD BGCOLOR="#ffff00"> - <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %> + <% $cust_main->invoicing_list_emailonly_scalar || $no %> </TD> </TR> -% } - -% if ( $cust_main->get("${pre}company") ) { +% } +% if ( $cust_main->company ) { <TR> <TD ALIGN="right"><% mt('Company') |h %></TD> - <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}company") |h %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->company |h %></TD> </TR> -% } - +% } +% } # if $this eq 'bill' +% # now the actual address <TR> <TD ALIGN="right"><% mt('Address') |h %></TD> - <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address1") |h %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $location->address1 |h %></TD> </TR> -% if ( $cust_main->get("${pre}address2") ) { -% my $address2_label = -% ( $conf->exists('cust_main-require_address2') -% && ! ( $pre xor $cust_main->has_ship_address ) -% ) -% ? emt('Unit #') -% : ' '; +% if ( $location->get('address2') ) { +% my $address2_label = $conf->exists('cust_main-require_address2') +% ? emt('Unit #') +% : ' '; - <TR> - <TD ALIGN="right"><% $address2_label %></TD> - <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->get("${pre}address2") |h %></TD> - </TR> +<TR> + <TD ALIGN="right"><% $address2_label %></TD> + <TD COLSPAN=7 BGCOLOR="#ffffff"><% $location->address2 |h %></TD> +</TR> % } <TR> <TD ALIGN="right"><% mt('City') |h %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}city") |h %></TD> -% if ( $cust_main->get("${pre}county") ) { + <TD BGCOLOR="#ffffff"><% $location->city |h %></TD> +% if ( $location->county ) { <TD ALIGN="right"><% mt('County') |h %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}county") |h %></TD> + <TD BGCOLOR="#ffffff"><% $location->county |h %></TD> % } <TD ALIGN="right"><% mt('State') |h %></TD> - <TD BGCOLOR="#ffffff"><% state_label( $cust_main->get("${pre}state"), $cust_main->get("${pre}country") ) |h %></TD> + <TD BGCOLOR="#ffffff"><% state_label( $location->state, $location->country ) |h %></TD> <TD ALIGN="right"><% mt('Zip') |h %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->get("${pre}zip") %></TD> + <TD BGCOLOR="#ffffff"><% $location->zip %></TD> </TR> <TR> <TD ALIGN="right"><% mt('Country') |h %></TD> - <TD BGCOLOR="#ffffff"><% code2country( $cust_main->get("${pre}country") ) %></TD> + <TD BGCOLOR="#ffffff"><% code2country( $location->country ) %></TD> </TR> -% if ( $cust_main->get($pre.'latitude') && $cust_main->get($pre.'longitude') ) { - <& /elements/tr-coords.html, $cust_main->get($pre.'latitude'), - $cust_main->get($pre.'longitude'), +% if ( $location->latitude && $location->longitude ) { + <& /elements/tr-coords.html, $location->latitude, + $location->longitude, $cust_main->name_short, $cust_main->agentnum, &> % } + +% if ( $this eq 'bill' ) { +% # billing contact phone numbers +% foreach my $phone (qw(daytime night mobile)) { +% next if !$cust_main->get($phone); +<TR> + <TD ALIGN="right"><% $phone_label{$phone} %></TD> + <TD COLSPAN=3 BGCOLOR="#ffffff"> + <& /elements/phonenumber.html, + $cust_main->get($phone), + callable => 1, + calling_list_exempt => $cust_main->calling_list_exempt, + &> + </TD> +</TR> -% foreach my $phone (grep $cust_main->get($pre.$_), qw( daytime night mobile )){ - - <TR> - <TD ALIGN="right"><% $phone_label{$phone} %></TD> - <TD COLSPAN=3 BGCOLOR="#ffffff"> - <& /elements/phonenumber.html, - $cust_main->get($pre.$phone), - 'callable'=>1, - 'calling_list_exempt'=>$cust_main->calling_list_exempt, - &> - </TD> - </TR> - -% } +% } #foreach $phone +% if ( $cust_main->get('fax') ) { -% if ( $cust_main->get("${pre}fax") ) { <TR> <TD ALIGN="right"><% mt('Fax') |h %></TD> <TD COLSPAN=3 BGCOLOR="#ffffff"> - <% $cust_main->get("${pre}fax") || ' ' %> + <% $cust_main->get('fax') || ' ' %> </TD> </TR> -% } -% if ( $which eq '' && $conf->exists('show_stateid') ) { - <TR> +% } +% +% if ( $conf->exists('show_stateid') ) { + +<TR> <TD ALIGN="right"><% $stateid_label %></TD> <TD BGCOLOR="#ffffff"><% $cust_main->masked('stateid') || ' ' %></TD> <TD ALIGN="right"><% $stateid_state_label %></TD> <TD BGCOLOR="#ffffff"><% $cust_main->stateid_state || ' ' %></TD> </TR> -% } +% } +% } #if $this eq 'bill' </TABLE> -% if ( $which ne 'ship_' ) { +% if ( @which ) { <BR> % } -% } +% } #while @which <%once> my %phone_label = ( @@ -147,7 +158,7 @@ my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_st </%once> <%init> -my( $cust_main ) = @_; +my $cust_main = shift; my $conf = new FS::Conf; my @invoicing_list = $cust_main->invoicing_list; my $no = emt('no'); diff --git a/httemplate/view/cust_main/custom_content.html b/httemplate/view/cust_main/custom_content.html new file mode 100644 index 000000000..dd3d1f0d4 --- /dev/null +++ b/httemplate/view/cust_main/custom_content.html @@ -0,0 +1,31 @@ +% foreach my $item (@content) { +% +% if ( $item =~ /^\s*$/ ) { + <BR> +% next; +% } +% +% if ( $items{$item} ) { + <& "custom_content/$item.html", $cust_main &> +% } else { + Unknown item <% $item |h %><BR> +% } +% } +<%init> + +my($cust_main) = @_; + +my $conf = new FS::Conf; + +my @content = $conf->config('cust_main-custom_content'); + +my %items = map { $_=>1 } qw( + small_custview + birthdate + spouse_birthdate + svc_acct + svc_phone + svc_hardware +); + +</%init> diff --git a/httemplate/view/cust_main/custom_content/birthdate.html b/httemplate/view/cust_main/custom_content/birthdate.html new file mode 100644 index 000000000..1f16963af --- /dev/null +++ b/httemplate/view/cust_main/custom_content/birthdate.html @@ -0,0 +1,15 @@ +<TABLE CLASS="fsinnerbox"> + <& /elements/tr-td-label.html, 'label' => mt('Date of Birth') &> + <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD> +</TR> +</TABLE> +<%init> +my($cust_main) = @_; +my $conf = new FS::Conf; +my $date_format = ($conf->config('date_format') || "%m/%d/%Y"); +my $dt = $cust_main->birthdate ne '' + ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate, + 'time_zone' =>'floating', + ) + : ''; +</%init> diff --git a/httemplate/view/cust_main/custom_content/small_custview.html b/httemplate/view/cust_main/custom_content/small_custview.html new file mode 100644 index 000000000..7c724c78f --- /dev/null +++ b/httemplate/view/cust_main/custom_content/small_custview.html @@ -0,0 +1,4 @@ +<& /elements/small_custview.html, $cust_main &> +<%init> +my($cust_main) = @_; +</%init> diff --git a/httemplate/view/cust_main/custom_content/spouse_birthdate.html b/httemplate/view/cust_main/custom_content/spouse_birthdate.html new file mode 100644 index 000000000..c78fd26a5 --- /dev/null +++ b/httemplate/view/cust_main/custom_content/spouse_birthdate.html @@ -0,0 +1,15 @@ +<TABLE CLASS="fsinnerbox"> + <& /elements/tr-td-label.html, 'label' => mt('Spouse Date of Birth') &> + <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD> +</TR> +</TABLE> +<%init> +my($cust_main) = @_; +my $conf = new FS::Conf; +my $date_format = ($conf->config('date_format') || "%m/%d/%Y"); +my $dt = $cust_main->spouse_birthdate ne '' + ? DateTime->from_epoch( 'epoch' => $cust_main->spouse_birthdate, + 'time_zone' =>'floating', + ) + : ''; +</%init> diff --git a/httemplate/view/cust_main/custom_content/svc_Common.html b/httemplate/view/cust_main/custom_content/svc_Common.html new file mode 100644 index 000000000..bddb8bf16 --- /dev/null +++ b/httemplate/view/cust_main/custom_content/svc_Common.html @@ -0,0 +1,40 @@ +% foreach my $cust_svc (@cust_svc) { +% my $svc_x = $cust_svc->svc_x; +<TABLE CLASS="fsinnerbox"> + <TR> + <TH COLSPAN=2><% $cust_svc->part_svc->svc |h %></TH> + </TR> +% foreach my $field ( grep $svc_x->$_(), @{ $opt{fields} } ) { + <& /elements/tr-td-label.html, 'label' => $labels{$field} &> + <TD BGCOLOR="#ffffff"><% $svc_x->$field() |h %></TD> + </TR> +% } +</TABLE> +% } +<%init> +my($cust_main, %opt) = @_; + +my $table = $opt{table}; +my @cust_svc = (); +foreach my $cust_pkg ( + grep $_->num_cust_svc( 'svcdb'=>$table ), + $cust_main->all_pkgs +) { + my @wtf = $cust_pkg->cust_svc( 'svcdb'=>$table ); + push @cust_svc, $cust_pkg->cust_svc( 'svcdb'=>$table ); +} + +my %labels; +if ( UNIVERSAL::can("FS::$table", 'table_info') ) { +# $opt{'name'} = "FS::$table"->table_info->{'name'}; + + my $fields = "FS::$table"->table_info->{'fields'}; + %labels = map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } + keys %$fields; +} + +</%init> diff --git a/httemplate/view/cust_main/custom_content/svc_acct.html b/httemplate/view/cust_main/custom_content/svc_acct.html new file mode 100644 index 000000000..49b97986d --- /dev/null +++ b/httemplate/view/cust_main/custom_content/svc_acct.html @@ -0,0 +1,7 @@ +<& svc_Common.html, $cust_main, + 'table' => 'svc_acct', + 'fields' => [qw( username _password )], +&> +<%init> +my($cust_main) = @_; +</%init> diff --git a/httemplate/view/cust_main/custom_content/svc_hardware.html b/httemplate/view/cust_main/custom_content/svc_hardware.html new file mode 100644 index 000000000..f5d53a251 --- /dev/null +++ b/httemplate/view/cust_main/custom_content/svc_hardware.html @@ -0,0 +1,7 @@ +<& svc_Common.html, $cust_main, + 'table' => 'svc_hardware', + 'fields' => [qw( ip_addr hw_addr serial )], +&> +<%init> +my($cust_main) = @_; +</%init> diff --git a/httemplate/view/cust_main/custom_content/svc_phone.html b/httemplate/view/cust_main/custom_content/svc_phone.html new file mode 100644 index 000000000..46ec476bc --- /dev/null +++ b/httemplate/view/cust_main/custom_content/svc_phone.html @@ -0,0 +1,7 @@ +<& svc_Common.html, $cust_main, + 'table' => 'svc_phone', + 'fields' => [qw( phonenum )], +&> +<%init> +my($cust_main) = @_; +</%init> diff --git a/httemplate/view/cust_main/locations.html b/httemplate/view/cust_main/locations.html index 98c933645..b29d0ce4d 100755 --- a/httemplate/view/cust_main/locations.html +++ b/httemplate/view/cust_main/locations.html @@ -5,12 +5,17 @@ span.loclabel { background-color: #cccccc; border: 1px solid black } +table.location { + width: 100%; + padding: 1px; + border-spacing: 0px; +} </STYLE> % foreach my $locationnum (@sorted) { % my $packages = $packages_in{$locationnum}; % my $loc = $locations{$locationnum}; % next if $loc->disabled and scalar(@$packages) == 0; -<& /elements/table-grid.html &> +<TABLE CLASS="grid location"> <TR><TH COLSPAN=3 ALIGN="left" VALIGN="bottom" STYLE="padding-bottom: 0px; padding-left: 0px; @@ -18,10 +23,7 @@ STYLE="padding-bottom: 0px; border-bottom-color: black; border-bottom-width: 1px;"> <SPAN CLASS="loclabel"> -% if (! $locationnum) { -<% mt('Default service location:') |h %> -% } -% elsif ( $loc->disabled ) { +% if ( $loc->disabled ) { <FONT COLOR="#808080"><I> % } <% $loc->location_label %></SPAN> @@ -49,8 +51,7 @@ my %locations = map { $_->locationnum => $_ } qsearch({ 'order_by' => 'ORDER BY country, state, city, address1, locationnum', }); my @sections = keys %locations; -$locations{''} = $cust_main; -my %packages_in = map { $_ => [] } ('', @sections); +my %packages_in = map { $_ => [] } (@sections); my %active = (); # groups with non-canceled packages foreach my $cust_pkg ( @$all_packages ) { @@ -58,10 +59,13 @@ foreach my $cust_pkg ( @$all_packages ) { push @{ $packages_in{$key} }, $cust_pkg; $active{$key} = 1 if !$cust_pkg->getfield('cancel'); } +# prevent disabling these +$active{$cust_main->ship_locationnum} = 1; +$active{$cust_main->bill_locationnum} = 1; my @sorted = ( - '', - grep ( { $active{$_} } @sections), + $cust_main->ship_locationnum, + grep ( { $active{$_} && $_ != $cust_main->ship_locationnum } @sections), grep ( { !$active{$_} } @sections), ); diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 295328721..263c266e7 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -102,6 +102,26 @@ <TD BGCOLOR="#ffffff"><% $cust_main->signupdate ? time2str($date_format, $cust_main->signupdate) : '' %></TD> </TR> +% my $id_country = $conf->config('national_id-country'); +% if ( $id_country ) { +% if ( $id_country eq 'MY' ) { + <TR> +% my($old, $nric) = ( '', ''); +% if ( $cust_main->national_id =~ /^\d{6}\-\d{2}\-\d{4}$/ ) { + <TD ALIGN="right"><% mt('NRIC') |h %></TD> +% } else { # elsif ( $cust_main->national_id =~ /^\w\d{9}$/ ) { + <TD ALIGN="right"><% mt('Old IC/Passport') |h %></TD> +% #} else { +% # warn "unknown national_id format"; +%# <TD ALIGN="right"></TD> +% } + <TD BGCOLOR="#ffffff"><% $cust_main->national_id |h %></TD> + </TR> +% } else { +% warn "unknown national_id-country $id_country"; +% } +% } + % if ( $conf->exists('cust_main-enable_birthdate') ) { % my $dt = $cust_main->birthdate ne '' % ? DateTime->from_epoch( 'epoch' => $cust_main->birthdate, @@ -116,13 +136,41 @@ % } +% if ( $conf->exists('cust_main-enable_spouse_birthdate') ) { +% my $dt = $cust_main->spouse_birthdate ne '' +% ? DateTime->from_epoch( 'epoch' => $cust_main->spouse_birthdate, +% 'time_zone' =>'floating', +% ) +% : ''; + + <TR> + <TD ALIGN="right"><% mt('Spouse Date of Birth') |h %></TD> + <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD> + </TR> + +% } + +% if ( $conf->exists('cust_main-enable_anniversary_date') ) { +% my $dt = $cust_main->anniversary_date ne '' +% ? DateTime->from_epoch( 'epoch' => $cust_main->anniversary_date, +% 'time_zone' =>'floating', +% ) +% : ''; + + <TR> + <TD ALIGN="right"><% mt('Anniversary Date') |h %></TD> + <TD BGCOLOR="#ffffff"><% $dt ? $dt->strftime($date_format) : '' %></TD> + </TR> + +% } + % if ( $conf->exists('cust_main-require_censustract') ) { <TR> <TD ALIGN="right"> - <% mt('Census tract ([_1])', $cust_main->censusyear) |h %> + <% mt('Census tract ([_1])', $cust_main->ship_location->censusyear) |h %> </TD> - <TD BGCOLOR="#ffffff"><% $cust_main->censustract %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->censustract %></TD> </TR> % } @@ -131,7 +179,7 @@ <TR> <TD ALIGN="right"><% mt('Tax district') |h %></TD> - <TD BGCOLOR="#ffffff"><% $cust_main->district %></TD> + <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->district %></TD> </TR> % } diff --git a/httemplate/view/cust_main/order_pkg_link.html b/httemplate/view/cust_main/order_pkg_link.html deleted file mode 100644 index 7bc5eef31..000000000 --- a/httemplate/view/cust_main/order_pkg_link.html +++ /dev/null @@ -1,22 +0,0 @@ -<& /elements/popup_link-cust_main.html, - 'action' => $p. 'misc/order_pkg.html', - 'label' => $opt{'label'} || emt('Order new package'), - 'actionlabel' => emt('Order new package'), - 'color' => '#333399', - 'cust_main' => $cust_main, - 'closetext' => emt('Close'), - 'width' => 960, #763, - 'height' => $height, - %optional, -&> -<%init> - -my($cust_main, %opt) = @_; - -my %optional = map { $_ => $opt{$_} } - grep $opt{$_}, - qw( lock_pkgpart lock_locationnum qualnum svcpart ); - -my $height = $opt{'lock_locationnum'} ? 336 : 576; - -</%init> diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index e16b2c1d4..7d7930634 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -7,7 +7,7 @@ % if ( $curuser->access_right('Order customer package') ) { <% $s++ ? ' | ' : '' %> - <& order_pkg_link.html, $cust_main &> + <& /elements/order_pkg_link.html, 'cust_main'=>$cust_main &> % } % if ( $curuser->access_right('One-time charge') diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html index 5f458e635..c0a56d0f3 100644 --- a/httemplate/view/cust_main/packages/services.html +++ b/httemplate/view/cust_main/packages/services.html @@ -3,7 +3,7 @@ % ### <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> - <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> + <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=2 WIDTH="100%"> <SCRIPT TYPE="text/javascript"> function clearhint_search_cust_svc(obj, str) { if (obj.value == str) obj.value = ''; diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 28df9da95..e9017745b 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -32,7 +32,20 @@ % } % -% } else { +% if ( $part_pkg->freq ) { #? + + <TR> + <TD COLSPAN=<%$colspan%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Un-cancel customer package') ) { + ( <% pkg_uncancel_link($cust_pkg) %> ) +% } + <FONT> + </TD> + </TR> +% } +% +% } else { % % if ( $cust_pkg->get('susp') ) { #status: suspended % my $cpr = $cust_pkg->last_cust_pkg_reason('susp'); @@ -56,9 +69,16 @@ <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %> % } + <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> + <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %> -% if ( $part_pkg->option('suspend_bill', 1) ) { +% if ( $cust_pkg->option('suspend_bill', 1) +% || ( $part_pkg->option('suspend_bill', 1) +% && ! $cust_pkg->option('no_suspend_bill',1) +% ) +% ) +% { <% pkg_status_row_if( $cust_pkg, emt('Next bill'), 'bill', %opt, curuser=>$curuser ) %> % } <% pkg_status_row_if( $cust_pkg, emt('Will resume'), 'resume', %opt, curuser=>$curuser ) %> @@ -99,6 +119,8 @@ ) %> + <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> + <TR> <TD COLSPAN=<%$colspan%>> <FONT SIZE=-1> @@ -118,6 +140,7 @@ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %> + <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> % } % @@ -133,6 +156,8 @@ <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> + % } else { % % my $num_cust_svc = $cust_pkg->num_cust_svc; @@ -166,6 +191,8 @@ <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %> + <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> + % } % % } @@ -467,6 +494,16 @@ sub pkg_cancel_link { ) } +sub pkg_uncancel_link { + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p. 'misc/cancel_pkg.html?method=uncancel', + 'label' => emt('Un-cancel'), + 'actionlabel' => emt('Un-cancel'), + #'color' => #? + 'cust_pkg' => shift, + ) +} + sub pkg_expire_link { include( '/elements/popup_link-cust_pkg.html', 'action' => $p. 'misc/cancel_pkg.html?method=expire', diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index c7a7c8024..166addbf4 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -277,7 +277,9 @@ % ? sprintf("$money_char\%.2f", $item->{'charge'}) % : exists($item->{'charge_nobal'}) % ? sprintf("$money_char\%.2f", $item->{'charge_nobal'}) -% : ''; +% : exists($item->{'void_charge'}) +% ? sprintf("<DEL>$money_char\%.2f</DEL>", $item->{'void_charge'}) +% : ''; % % my $payment = exists($item->{'payment'}) % ? sprintf("- $money_char\%.2f", $item->{'payment'}) @@ -428,6 +430,15 @@ foreach my $cust_bill ($cust_main->cust_bill) { $num_cust_bill++; } +#voided invoices +foreach my $cust_bill_void ($cust_main->cust_bill_void) { + push @history, { + 'date' => $cust_bill_void->_date, + 'desc' => include('payment_history/voided_invoice.html', $cust_bill_void, %opt ), + 'void_charge' => $cust_bill_void->charged, + }; +} + #statements foreach my $cust_statement ($cust_main->cust_statement) { push @history, { @@ -491,7 +502,7 @@ foreach my $cust_credit ($cust_main->cust_credit) { foreach my $cust_refund ($cust_main->cust_refund) { push @history, { 'date' => $cust_refund->_date, - 'desc' => include('payment_history/refund.html', $cust_refund), + 'desc' => include('payment_history/refund.html', $cust_refund, %opt), 'refund' => $cust_refund->refund, }; diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html index 3028f0f69..96a9f5456 100644 --- a/httemplate/view/cust_main/payment_history/invoice.html +++ b/httemplate/view/cust_main/payment_history/invoice.html @@ -1,4 +1,4 @@ -<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $under %> +<% $link %><% $invoice %><% $link ? '</A>' : '' %><% "$void$delete$under" %> <%init> my( $cust_bill, %opt ) = @_; @@ -26,6 +26,18 @@ my $link = $curuser->access_right('View invoices') ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">! : ''; +my $void = ''; +if ( $cust_bill->closed !~ /^Y/i && $curuser->access_right('Void invoices') ) { + $void = + ' ('. include('/elements/popup_link.html', + 'label' => emt('void'), + 'action' => "${p}misc/void-cust_bill.html?;invnum=". + $cust_bill->invnum, + 'actionlabel' => emt('Void Invoice'), + ). + ')'; +} + my $delete = ''; $delete = areyousure_link("${p}misc/delete-cust_bill.html?$invnum", emt('Are you sure you want to delete this invoice?'), diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html index d7322a2d6..ff269bfaf 100644 --- a/httemplate/view/cust_main/payment_history/payment.html +++ b/httemplate/view/cust_main/payment_history/payment.html @@ -181,7 +181,7 @@ $void = areyousure_link("${p}misc/void-cust_pay.cgi?".$cust_pay->paynum, && $curuser->access_right('Echeck void') ) || ( $cust_pay->payby !~ /^(CARD|CHEK)$/ - && $curuser->access_right('Regular void') + && $curuser->access_right('Void payments') ) ) ); diff --git a/httemplate/view/cust_main/payment_history/voided_invoice.html b/httemplate/view/cust_main/payment_history/voided_invoice.html new file mode 100644 index 000000000..15393cbf5 --- /dev/null +++ b/httemplate/view/cust_main/payment_history/voided_invoice.html @@ -0,0 +1,57 @@ +<DEL><% $link %><% $invoice %><% $link ? '</A>' : '' %></DEL> +<I><% mt("voided [_1]", time2str($date_format, $cust_bill_void->void_date) ) |h %> +% my $void_user = $cust_bill_void->void_access_user; +% if ($void_user) { + by <% $void_user->username %></I> +% } +<% "$unvoid$delete$under" %> +<%init> + +my( $cust_bill_void, %opt ) = @_; + +my $date_format = $opt{'date_format'} || '%m/%d/%Y'; + +my $conf = new FS::Conf; + +my $curuser = $FS::CurrentUser::CurrentUser; + +my $invoice = emt("Invoice #[_1] (Balance [_2])",$cust_bill_void->display_invnum, $cust_bill_void->charged); + +my $under = ''; + +my $invnum = $cust_bill_void->invnum; + +my $link = $curuser->access_right('View invoices') + ? qq!<A HREF="${p}view/cust_bill_void.html?$invnum">! + : ''; + +my $unvoid = ''; +$unvoid = areyousure_link("${p}misc/unvoid-cust_bill_void.html?invnum=". $cust_bill_void->invnum, + emt('Are you sure you want to unvoid this invoice?'), + emt('Unvoid this invoice'), + emt('unvoid') + ) + if $cust_bill_void->closed !~ /^Y/ && $curuser->access_right('Unvoid invoices'); + +my $delete = ''; +$delete = areyousure_link("${p}misc/delete-cust_bill.html?$invnum", + emt('Are you sure you want to delete this invoice?'), + emt('Delete this invoice from the database completely'), + emt('delete') + ) + if ( $opt{'deleteinvoices'} && $curuser->access_right('Delete invoices') ); + +my $events = ''; +#1.9 +if ( $cust_bill_void->num_cust_event + && ( $curuser->access_right('Billing event reports') + || $curuser->access_right('View customer billing events') + ) + ) { + $under .= + qq!<BR><A HREF="${p}search/cust_event.html?invnum=$invnum">( !. + emt('View invoice events').' )</A>'; +} +$under = '<FONT SIZE="-1">'.$under.'</FONT>' if length($under); + +</%init> diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html index 2f038be41..88b5e0a84 100644 --- a/httemplate/view/cust_main/payment_history/voided_payment.html +++ b/httemplate/view/cust_main/payment_history/voided_payment.html @@ -31,6 +31,6 @@ $unvoid = areyousure_link("${p}misc/unvoid-cust_pay_void.cgi?".$cust_pay_void->p emt('Unvoid this payment from the database') . $unvoidmsg, emt('unvoid') ) - if ( $cust_pay_void->closed !~ /^Y/i && $curuser->access_right('Unvoid') ); + if ( $cust_pay_void->closed !~ /^Y/i && $curuser->access_right('Unvoid payments') ); </%init> diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html index 194e90742..f076fcc92 100644 --- a/httemplate/view/cust_main/tickets.html +++ b/httemplate/view/cust_main/tickets.html @@ -1,164 +1,2 @@ -<FORM METHOD="GET" NAME="CreateTicketForm" STYLE="display:inline"> -<SCRIPT TYPE="text/javascript"> -function updateTicketLink() { - var link = document.getElementById('CreateTicketLink'); - var selector = document.getElementById('Queue') - link.href = "<% $new_base.'?'. - join(';', map( - { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"} - keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value; -} -</SCRIPT> -<A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A> -<A id="CreateTicketLink" HREF="<% $new_link %>"><% mt('Create new ticket') |h %></A> - <% mt('in queue') |h %> -%# fetch list of queues in which the user can create tickets -% my %queues = FS::TicketSystem->queues('', 'CreateTicket'); -% if( $conf->exists('ticket_system-force_default_queueid') ) { -<B><% $queues{$new_param{'Queue'}} %></B> -<INPUT TYPE="hidden" NAME="Queue" VALUE="<% $new_param{'Queue'} %>"> -% } -% else { -<SELECT NAME="Queue" id="Queue" onchange="updateTicketLink()"> -% foreach my $queueid ( sort { $queues{$a} cmp $queues{$b} } keys %queues ) { - <OPTION VALUE="<% $queueid %>" - <% $queueid == $new_param{'Queue'} ? 'SELECTED' : '' %> - ><% $queues{$queueid} |h %> -% } -</SELECT> -<SCRIPT DEFER TYPE="text/javascript">updateTicketLink();</SCRIPT> -% } -</FORM> - | -View -<A HREF="<% $open_link %>"><% mt($openlabel) |h %></A> | -<A HREF="<% $res_link %>"><% mt('resolved') |h %></A> -<BR> - -<& /elements/table-grid.html &> -% my $bgcolor1 = '#eeeeee'; -% my $bgcolor2 = '#ffffff'; -% my $bgcolor = ''; - -<TR> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Queue') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Owner') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Due') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Estimated Time') |h %></TH> - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Priority') |h %></TH> -% if ( $ss_priority ) { - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Customer Priority') |h %></TH> -% } -</TR> - -% foreach my $ticket ( @tickets ) { -% my $href = FS::TicketSystem->href_ticket($ticket->{id}); -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } - - <TR> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <A HREF=<%$href%>><% $ticket->{id} %></A> - </TD> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <A HREF=<%$href%>><% $ticket->{subject} %></A> - </TD> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{status} %> - </TD> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{queue} %> - </TD> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{owner} %> - </TD> - - <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $date_formatter->($ticket->{due}) %> - </TD> - - <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{timeestimated} %> - </TD> - - <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{content} - ? $ticket->{content}.' ('.$ticket->{priority}.')' - : $ticket->{priority} - %> - </TD> - -% if ( $ss_priority ) { - <TD ALIGN="right" CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <% $ticket->{"CF.{$ss_priority}"} %> - </TD> -% } - - </TR> - -% } - -</TABLE> - -<%init> -use Date::Format 'time2str'; -use Date::Parse 'str2time'; - -my( $conf ) = new FS::Conf; -my( $cust_main ) = @_; -my( @tickets ) = $cust_main->tickets; - -my $open_link = FS::TicketSystem->href_customer_tickets($cust_main->custnum); -my $openlabel = join('/', FS::TicketSystem->statuses ); - -my $res_link = FS::TicketSystem->href_customer_tickets( - $cust_main->custnum, - { 'statuses' => [ 'resolved' ] } - ); - -my( $new_base, %new_param ) = - FS::TicketSystem->href_params_new_ticket( $cust_main ); - -my $new_link = FS::TicketSystem->href_new_ticket( $cust_main ); - -my $ss_priority = FS::TicketSystem->selfservice_priority; -if ( $ss_priority ) { - my $dir = $conf->exists('ticket_system-priority_reverse') ? -1 : 1; - use sort 'stable'; - # sort in the following way: - @tickets = sort { - # within a severity level... - ( $a->{'content'} eq $b->{'content'} ) ? ( - # no-priority tickets sort last - ( - ($a->{'_selfservice_priority'} eq '') <=> - ($b->{'_selfservice_priority'} eq '') - ) || - # otherwise obey ticket_system-priority_reverse - ( $dir * - ($b->{'_selfservice_priority'} <=> $a->{'_selfservice_priority'}) - ) - ) : 0; # but don't rearrange between severity levels - } @tickets; -} - -my $format = $conf->config('date_format') || '%Y-%m-%d'; - -my $date_formatter = sub { - my $time = str2time($_[0], 'GMT'); - # exclude times within 24 hours of zero - ($time > 86400) ? time2str($format, $time) : ''; -}; - -</%init> +% my $cust_main = shift; +<& /elements/table-tickets.html, object => $cust_main &> diff --git a/httemplate/view/cust_main_statement-pdf.cgi b/httemplate/view/cust_main_statement-pdf.cgi index 7a0e19838..7c2c20799 100755 --- a/httemplate/view/cust_main_statement-pdf.cgi +++ b/httemplate/view/cust_main_statement-pdf.cgi @@ -23,13 +23,17 @@ my $cust_main = qsearchs({ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, }); die "Customer #$custnum not found!" unless $cust_main; +my $cust_bill = ($cust_main->cust_bill)[-1] + or die "Customer #$custnum has no invoices!"; my $cust_statement = FS::cust_statement->new({ 'custnum' => $custnum, - 'statementnum' => 'ALL', #magic +# 'statementnum' => 'ALL', #magic + 'invnum' => $cust_bill->invnum, '_date' => time, }); + my $pdf = $cust_statement->print_pdf( '', $templatename ); http_header('Content-Type' => 'application/pdf' ); diff --git a/httemplate/view/cust_msg.html b/httemplate/view/cust_msg.html index ec584605d..67ceef799 100755 --- a/httemplate/view/cust_msg.html +++ b/httemplate/view/cust_msg.html @@ -22,7 +22,7 @@ function toggle_display(obj) { <INPUT type="radio" name="what_to_show" onchange="toggle_display(this)" value="body"> Body </FORM> </TR> -<TR><TD colspan=2 style="text-align:center"> +<TR><TD colspan=2 style="text-align:left"> <TEXTAREA id="content-header" style="font-family:monospace" readonly=1 cols=80 rows=20> <% encode_entities($cust_msg->header) %> diff --git a/httemplate/view/directions.html b/httemplate/view/directions.html index 599d049c2..f14a11a07 100644 --- a/httemplate/view/directions.html +++ b/httemplate/view/directions.html @@ -22,7 +22,6 @@ body { height: 100%; margin: 0px; padding: 0px } #map_canvas { height: 100%; - margin-right: 320px; } #directions_panel { diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index 3130c73a0..04d2b2962 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -114,6 +114,10 @@ function areyousure(href) { % } +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/elements/tr-svc_export_machine.html b/httemplate/view/elements/tr-svc_export_machine.html new file mode 100644 index 000000000..1ba8d74a1 --- /dev/null +++ b/httemplate/view/elements/tr-svc_export_machine.html @@ -0,0 +1,27 @@ +% foreach my $part_export (@part_export) { +% my $label = ( $part_export->exportname +% ? $part_export->exportname +% : $part_export->label +% ). +% ' hostname'; +% +% my $svc_export_machine = qsearchs('svc_export_machine', { +% 'svcnum' => $opt{svc}->svcnum, +% 'exportnum' => $part_export->exportnum, +% }); + + <& tr.html, + 'label' => $label, + 'value' => $svc_export_machine + ? $svc_export_machine->part_export_machine->machine + : '', + &> +% } +<%init> + +my %opt = @_; + +my @part_export = grep { $_->machine eq '_SVC_MACHINE' } + $opt{part_svc}->part_export; + +</%init> diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html index 9e85348af..6c4595dbe 100644 --- a/httemplate/view/prospect_main.html +++ b/httemplate/view/prospect_main.html @@ -64,6 +64,38 @@ <BR> +% if ( $curuser->access_right('Generate quotation') ) { + <FONT CLASS="fsinnerbox-title"><% mt( 'Quotations' ) |h %></FONT> + <A HREF="<%$p%>edit/quotation.html?prospectnum=<% $prospectnum %>">New quotation</A> +% my @quotations = $prospect_main->quotation; +% if ( @quotations ) { + <& /elements/table-grid.html &> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc">#</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Date') |h %></TH> + </TR> +% foreach my $quotation (@quotations) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% my $a = qq(<A HREF="$p/view/quotation.html?quotationnum=). #" +% $quotation->quotationnum. '">'; + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $a %><% $quotation->quotationnum %></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $a %><% time2str($date_format, $quotation->_date) |h %></A></TD> + </TR> +% } + </TABLE> +% } + <BR><BR> +% } + + % if ( $curuser->access_right('Qualify service') ) { <% include( '/elements/popup_link-prospect_main.html', 'action' => $p. 'misc/qual.html', @@ -80,6 +112,7 @@ <BR><BR> % } +<!-- <% ntable("#cccccc") %> <TR> @@ -87,6 +120,7 @@ </TR> </TABLE> +--> <%init> @@ -95,6 +129,10 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('View prospect'); +my $conf = new FS::Conf; + +my $date_format = $conf->config('date_format') || '%m/%d/%Y'; + my $prospectnum; if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { $prospectnum = $1; diff --git a/httemplate/view/qual.cgi b/httemplate/view/qual.cgi index 5c15ec0c3..56c2b61df 100644 --- a/httemplate/view/qual.cgi +++ b/httemplate/view/qual.cgi @@ -59,8 +59,10 @@ % $opt{'svcpart'} = $pkg_svc->svcpart if $pkg_svc; % } - <% include('/view/cust_main/order_pkg_link.html', - $cust_or_prospect, %opt) %> + <& /elements/order_pkg_link.html, + 'cust_main' => $cust_or_prospect, + %opt + &> % } elsif ($cust_or_prospect->prospectnum) { diff --git a/httemplate/view/quotation.html b/httemplate/view/quotation.html new file mode 100755 index 000000000..a88acf82b --- /dev/null +++ b/httemplate/view/quotation.html @@ -0,0 +1,91 @@ +<& /elements/header.html, mt('Quotation View'), $menubar &> + +%#XXX link to order... + +<%doc> + +XXX resending quotations + +% if ( $curuser->access_right('Resend invoices') ) { + + <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A> + +% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { + | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>"><% mt('Re-email this invoice') |h %></A> +% } + +% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { + | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>"><% mt('Re-fax this invoice') |h %></A> +% } + + <BR><BR> + +% } + +</%doc> + +% if ( $curuser->access_right('Order customer package') ) { + <& /elements/order_pkg_link.html, + 'label' => emt('Add package'), + 'actionlabel' => emt('Add package'), + map { $_ => $quotation->$_ } qw( quotationnum custnum prospectnum ) + &> +% } + +% if ( $conf->exists('quotation_latex') ) { + | <A HREF="<% $p %>view/quotation-pdf.cgi?<% $link %>"><% mt('View typeset quotation PDF') |h %></A> + <BR><BR> +% } + +% if ( $conf->exists('quotation_html') ) { + <% join('', $quotation->print_html() ) %> +% } else { +% die "quotation_html config missing"; +% } +% #plaintext quotations? <PRE><% join('', $quotation->print_text() ) %></PRE> + +<& /elements/footer.html &> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +#die "access denied" +# unless $curuser->access_right('View quotations'); + +my $quotationnum; +my($query) = $cgi->keywords; +if ( $query =~ /^(\d+)$/ ) { + $quotationnum = $1; +} else { + $quotationnum = $cgi->param('quotationnum'); +} + +my $conf = new FS::Conf; + +my $quotation = qsearchs({ + 'select' => 'quotation.*', + 'table' => 'quotation', + #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'quotationnum' => $quotationnum }, + #'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +die "Quotation #$quotationnum not found!" unless $quotation; + +my $menubar; +if ( my $custnum = $quotation->custnum ) { + my $display_custnum = $quotation->cust_main->display_custnum; + $menubar = menubar( + emt("View this customer (#[_1])",$display_custnum) => "${p}view/cust_main.cgi?$custnum", + ); +} elsif ( my $prospectnum = $quotation->prospectnum ) { + $menubar = menubar( + emt("View this prospect (#[_1])",$prospectnum) => "${p}view/prospect_main.html?$prospectnum", + ); +} + +my $link = "quotationnum=$quotationnum"; +#$link .= ';template='. uri_escape($template) if $template; +#$link .= ';notice_name='. $notice_name if $notice_name; + + +</%init> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 441e4945d..3ad21bb4d 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -47,6 +47,7 @@ <& svc_acct/basics.html, 'svc_acct' => $svc_acct, 'part_svc' => $part_svc, + 'cust_svc' => $cust_svc, %gopt, &> @@ -75,6 +76,11 @@ <& elements/svc_export_settings.html, $svc_acct &> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + +<BR> <% joblisting({'svcnum'=>$svcnum}, 1) %> <& /elements/footer.html &> diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html index 8f180b648..1cdf77615 100644 --- a/httemplate/view/svc_acct/basics.html +++ b/httemplate/view/svc_acct/basics.html @@ -1,6 +1,9 @@ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> <& /view/elements/tr.html, label=>mt('Service'), value=>$part_svc->svc &> +% if ( $opt{cust_svc}->agent_svcid ) { + <& /view/elements/tr.html, label=>mt('Legacy ID'), value=>$opt{cust_svc}->agent_svcid &> +% } <& /view/elements/tr.html, label=>mt('Username'), value=>$svc_acct->username &> <& /view/elements/tr.html, label=>mt('Domain'), value=>$domain &> @@ -53,6 +56,11 @@ &> % } +<& /view/elements/tr-svc_export_machine.html, + 'svc' => $svc_acct, + 'part_svc' => $part_svc, +&> + % if ($svc_acct->uid ne '') { <& /view/elements/tr.html, label=>mt('UID'), value=>$svc_acct->uid &> % } diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi index 3938a3406..fcccd74b4 100755 --- a/httemplate/view/svc_domain.cgi +++ b/httemplate/view/svc_domain.cgi @@ -36,6 +36,10 @@ <% include('elements/svc_export_settings.html', $svc_domain) %> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi index 77679d81c..72e553598 100644 --- a/httemplate/view/svc_external.cgi +++ b/httemplate/view/svc_external.cgi @@ -23,6 +23,11 @@ </TABLE></TD></TR></TABLE> + +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <BR><% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi index 15b5ae56f..2cb78ebd5 100755 --- a/httemplate/view/svc_forward.cgi +++ b/httemplate/view/svc_forward.cgi @@ -53,6 +53,10 @@ <% include('elements/svc_export_settings.html', $svc_forward) %> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi index 935d139e9..fbb02a00f 100644 --- a/httemplate/view/svc_www.cgi +++ b/httemplate/view/svc_www.cgi @@ -49,6 +49,10 @@ </TABLE> <BR> +% if ( $conf->config('ticket_system') ) { +<& /elements/table-tickets.html, object => $cust_svc &> +% } + <% joblisting({'svcnum'=>$svcnum}, 1) %> <% include('/elements/footer.html') %> |