diff options
Diffstat (limited to 'httemplate/search')
52 files changed, 7874 insertions, 0 deletions
diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html new file mode 100644 index 000000000..0facc7fab --- /dev/null +++ b/httemplate/search/cdr.html @@ -0,0 +1,91 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'call detail records', + 'query' => { 'table' => 'cdr', + 'hashref' => $hashref, + 'extra_sql' => $qsearch, + 'order_by' => 'ORDER BY calldate', + }, + 'count_query' => $count_query, + 'header' => [ fields('cdr') ], #XXX fill in some nice names + 'fields' => [ fields('cdr') ], #XXX fill in some pretty-print + # processing, etc. + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +my $title = 'Call Detail Records'; +my $hashref = {}; + +#process params for CDR search, populate $hashref... +# and fixup $count_query + +my @search = (); +my @qsearch = (); + +### +# freesidestatus +### + +if ( $cgi->param('freesidestatus') eq 'NULL' ) { + + my $title = "Unprocessed $title"; + $hashref->{'freesidestatus'} = ''; # Record.pm will take care of it + push @search, "( freesidestatus IS NULL OR freesidestatus = '' )"; + +} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) { + + my $title = "Processed $title"; + $hashref->{'freesidestatus'} = $1; + push @search, "freesidestatus = '$1'"; + +} + +### +# dates +### + +my $str2time_sql = str2time_sql; + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, +my @dsearch = ( "$str2time_sql calldate) >= $beginning ", + "$str2time_sql calldate) <= $ending" + ); +push @search, @dsearch; +push @qsearch, @search; + + +### +# src/dest +### + +if ( $cgi->param('src') =~ /^\s*([\d\-\+\ ]+)\s*$/ ) { + ( my $src = $1 ) =~ s/\D//g; + $hashref->{'src'} = $src; + push @search, "src = '$src'"; +} + +if ( $cgi->param('dst') =~ /^\s*([\d\-\+ ]+)\s*$/ ) { + ( my $dst = $1 ) =~ s/\D//g; + $hashref->{'dst'} = $dst; + push @search, "dst = '$dst'"; +} + +### +# finish it up +### + +my $search = join(' AND ', @search); +$search = "WHERE $search" if $search; + +my $count_query = "SELECT COUNT(*) FROM cdr $search"; + +my $qsearch = join(' AND ', @qsearch); +$qsearch = ( scalar(keys %$hashref) ? ' AND ' : ' WHERE ' ) . $qsearch + if $qsearch; + +</%init> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html new file mode 100755 index 000000000..9166f6dcb --- /dev/null +++ b/httemplate/search/cust_bill.html @@ -0,0 +1,226 @@ +<% include( 'elements/search.html', + 'title' => 'Invoice Search Results', + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'invoices', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => $count_addl, + 'redirect' => $link, + 'header' => [ 'Invoice #', + 'Balance', + 'Net Amount', + 'Gross Amount', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'invnum', + sub { sprintf($money_char.'%.2f', shift->get('owed') ) }, + sub { sprintf($money_char.'%.2f', shift->get('net') ) }, + sub { sprintf($money_char.'%.2f', shift->charged ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'rrrr'.FS::UI::Web::cust_aligns(), + 'links' => [ + $link, + $link, + $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + + + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); + +my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )'; +#here is the agent virtualization +my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; + +my( $count_query, $sql_query ); +my $count_addl = ''; +#my $distinct = ''; +my %search; + +if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { + + $count_query = + "SELECT COUNT(*) FROM cust_bill $join_cust_main". + " WHERE invnum = $2 AND $agentnums_sql"; #agent virtualization + $sql_query = { + 'table' => 'cust_bill', + 'addl_from' => $join_cust_main, + 'hashref' => { 'invnum' => $2 }, + #'select' => '*', + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + }; + +} else { + + #some false laziness w/cust_bill::re_X + my @where; + my $orderby = 'ORDER BY cust_bill._date'; + + if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $search{'begin'} = str2time($1); + } + if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $search{'end'} = str2time($1) + 86399; + } + + if ( $cgi->param('begin') =~ /^(\d+)$/ ) { + $search{'begin'} = $1; + } + if ( $cgi->param('end') =~ /^(\d+)$/ ) { + $search{'end'} = $1; + } + + if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) { + $search{'invnum_min'} = $1; + } + if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) { + $search{'invnum_max'} = $1; + } + + if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $search{'agentnum'} = $1; + } + + $search{'open'} = 1 if $cgi->param('open'); + $search{'net'} = 1 if $cgi->param('net' ); + + 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::cust_bill->search_sql( \%search ); + + unless ( $count_query ) { + $count_query = 'SELECT COUNT(*), '. join(', ', + map "SUM($_)", + ( 'charged', + FS::cust_bill->net_sql, + FS::cust_bill->owed_sql, + ) + ); + $count_addl = [ '$%.2f invoiced (gross)', + '$%.2f invoiced (net)', + '$%.2f outstanding balance', + ]; + } + $count_query .= " FROM cust_bill $join_cust_main $extra_sql"; + + $sql_query = { + 'table' => 'cust_bill', + 'addl_from' => $join_cust_main, + 'hashref' => {}, + #'select' => "$distinct ". join(', ', + 'select' => join(', ', + 'cust_bill.*', + #( map "cust_main.$_", qw(custnum last first company) ), + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + FS::cust_bill->owed_sql. ' AS owed', + FS::cust_bill->net_sql. ' AS net', + ), + 'extra_sql' => "$extra_sql $orderby" + }; + +} + +my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ]; +my $clink = sub { + my $cust_bill = shift; + $cust_bill->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $html_init = join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ 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 qq!<INPUT TYPE="hidden" NAME="$_" VALUE="$search{$_}">!, keys %search ), + qq!</FORM>! +} qw( print_ email_ fax_ ) ). + +'<SCRIPT TYPE="text/javascript"> + +function confirm_print_process() { + if ( ! confirm("Are you sure you want to reprint these invoices?") ) { + return; + } + print_process(); +} +function confirm_email_process() { + if ( ! confirm("Are you sure you want to re-email these invoices?") ) { + return; + } + email_process(); +} +function confirm_fax_process() { + if ( ! confirm("Are you sure you want to re-fax these invoices?") ) { + return; + } + fax_process(); +} + +</SCRIPT>'; + +my $menubar = [ + 'Print these invoices' => + "javascript:confirm_print_process()", + 'Email these invoices' => + "javascript:confirm_email_process()", + ]; + +push @$menubar, 'Fax these invoices' => + "javascript:confirm_fax_process()" + if $conf->exists('hylafax'); + +</%init> diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi new file mode 100644 index 000000000..ff4168d6d --- /dev/null +++ b/httemplate/search/cust_bill_event.cgi @@ -0,0 +1,166 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'billing events', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Event', + 'Date', + 'Status', + #'Inv #', 'Inv Date', 'Cust #', + 'Invoice', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'event', + sub { time2str("%b %d %Y %T", $_[0]->_date) }, + sub { + #my $cust_bill_event = shift; + my $status = $_[0]->status; + $status .= ': '.$_[0]->statustext + if $_[0]->statustext; + $status; + }, + sub { + #my $cust_bill_event = shift; + 'Invoice #'. $_[0]->invnum. + ' ('. + time2str("%D", $_[0]->cust_bill_date). + ')'; + }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'lrlr'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + '', + sub { + my $part_bill_event = shift; + my $template = $part_bill_event->templatename; + $template .= '-' if $template; + [ "${p}view/cust_bill.cgi?$template", 'invnum']; + }, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + 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('Billing event reports') + or $curuser->access_right('View customer billing events') + && $cgi->param('invnum') =~ /^(\d+)$/; + +my $title = $cgi->param('failed') + ? 'Failed invoice events' + : 'Invoice events'; + +my %search = (); + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $search{agentnum} = $1; +} + +($search{beginning}, $search{ending}) + = FS::UI::Web::parse_beginning_ending($cgi); + +if ( $cgi->param('failed') ) { + $search{failed} = '1'; +} + +if ( $cgi->param('part_bill_event.payby') =~ /^(\w+)$/ ) { + $search{payby} = $1; +} + +if ( $cgi->param('invnum') =~ /^(\d+)$/ ) { + $search{invnum} = $1; +} + +my $where = 'WHERE '. FS::cust_bill_event->search_sql( \%search ); + +my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '. + 'LEFT JOIN cust_bill USING ( invnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) '; + +my $sql_query = { + 'table' => 'cust_bill_event', + 'select' => join(', ', + 'cust_bill_event.*', + 'part_bill_event.event', + 'cust_bill.custnum', + 'cust_bill._date AS cust_bill_date', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$where ORDER BY _date ASC", + 'addl_from' => $join, +}; + +my $count_sql = "SELECT COUNT(*) FROM cust_bill_event $join $where"; + +my $conf = new FS::Conf; + +my $html_init = ' + <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken o +n open invoices. See Reports->Billing events->Billing events for current event reports.</FONT><BR><BR>'; + +$html_init .= join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ keys(%search) ], + "../misc/${_}invoice_events.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $_, #key + ), + qq!<FORM NAME="${_}form">!, + qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though + (map {qq!<INPUT TYPE="hidden" NAME="$_" VALUE="$search{$_}">!} keys(%search)), + qq!</FORM>! +} qw( print_ email_ fax_ ) ); + +my $menubar = []; + +if ( $curuser->access_right('Resend invoices') ) { + + push @$menubar, 'Re-print these events' => + "javascript:print_process()", + 'Re-email these events' => + "javascript:email_process()", + ; + + push @$menubar, 'Re-fax these events' => + "javascript:fax_process()" + if $conf->exists('hylafax'); + +} + +my $link_cust = sub { + my $cust_bill_event = shift; + $cust_bill_event->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +</%init> diff --git a/httemplate/search/cust_bill_event.html b/httemplate/search/cust_bill_event.html new file mode 100755 index 000000000..87bb3b7b3 --- /dev/null +++ b/httemplate/search/cust_bill_event.html @@ -0,0 +1,67 @@ +<% include( + '/elements/header.html', + ( $cgi->param('failed') ? 'Failed invoice events' : 'Invoice events' ), + ) +%> + + <FONT SIZE="+1">Invoice events are the deprecated, old-style actions taken + on open invoices. See Reports->Billing events->Billing events for current event reports.</FONT><BR><BR> + + <FORM ACTION="cust_bill_event.cgi" METHOD="GET"> + <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') ? 1 : 0 %>"> + <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <!--<TR> + <TD ALIGN="right">Customer type</TD> + <TD><SELECT MULTIPLE NAME="perhaps_payby"> + <OPTION SELECTED VALUE="CARD">Credit card (automatic) + <OPTION SELECTED VALUE="CHEK">E-check (automatic) + <OPTION SELECTED VALUE="LECB">Phone bill billing + <OPTION SELECTED VALUE="BILL">Billing + <OPTION SELECTED VALUE="DCRD">Credit card (on-demand) + <OPTION SELECTED VALUE="DCHK">E-check (on-demand) + </TD> + </TR> + --> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <!-- + <TR> + <TD ALIGN="right">Events: </TD> + <TD> + <SELECT NAME="eventpart"> + <OPTION SELECTED VALUE=""><% $cgi->param('failed') ? '(all failed events)' : '(all events)' %> +% #foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) { +% #} + + </SELECT> + </TD> + </TR> + --> + <TR> + <TD ALIGN="right">Events for payment type: </TD> + <TD> + <SELECT NAME="part_bill_event.payby"> + <OPTION SELECTED VALUE="">(all) + <OPTION VALUE="CARD">Credit card (automatic) + <OPTION VALUE="BILL">Billing + <OPTION VALUE="CHEK">Electronic check (automatic) + <OPTION VALUE="DCRD">Credit card (on-demand) + <OPTION VALUE="DCHK">Electronic check (on-demand) + <OPTION VALUE="LECB">Phone bill billing + <OPTION VALUE="COMP">Complimentary + </SELECT> + </TD> + </TR> + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports'); + +</%init> diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi new file mode 100644 index 000000000..17b4bc240 --- /dev/null +++ b/httemplate/search/cust_bill_pkg.cgi @@ -0,0 +1,191 @@ +<% include( 'elements/search.html', + 'title' => 'Line items', + 'name' => 'line items', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Description', + 'Setup charge', + 'Recurring charge', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'billpkgnum', + sub { $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + }, + #strikethrough or "N/A ($amount)" or something these when + # they're not applicable to pkg_tax search + sub { sprintf($money_char.'%.2f', shift->setup ) }, + sub { sprintf($money_char.'%.2f', shift->recur ) }, + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + '', + $ilink, + $ilink, + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; + +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $where = " WHERE _date >= $beginning AND _date <= $ending "; + +$where .= " AND payby != 'COMP' " + unless $cgi->param('include_comp_cust'); + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $where .= " AND agentnum = $1 "; +} + +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + if ( $1 == 0 ) { + $where .= " AND classnum IS NULL "; + } else { + $where .= " AND classnum = $1 "; + } +} + +if ( $cgi->param('out') ) { + + $where .= " + AND 0 = ( + SELECT COUNT(*) FROM cust_main_county + WHERE ( cust_main_county.county = cust_main.county + OR ( cust_main_county.county IS NULL AND cust_main.county = '' ) + OR ( cust_main_county.county = '' AND cust_main.county IS NULL) + OR ( cust_main_county.county IS NULL AND cust_main.county IS NULL) + ) + AND ( cust_main_county.state = cust_main.state + OR ( cust_main_county.state IS NULL AND cust_main.state = '' ) + OR ( cust_main_county.state = '' AND cust_main.state IS NULL ) + OR ( cust_main_county.state IS NULL AND cust_main.state IS NULL ) + ) + AND cust_main_county.country = cust_main.country + AND cust_main_county.tax > 0 + ) + "; + +} elsif ( $cgi->param('country' ) ) { + + my $county = dbh->quote( $cgi->param('county') ); + my $state = dbh->quote( $cgi->param('state') ); + my $country = dbh->quote( $cgi->param('country') ); + $where .= " + AND ( county = $county OR $county = '' ) + AND ( state = $state OR $state = '' ) + AND country = $country + "; + $where .= ' AND taxclass = '. dbh->quote( $cgi->param('taxclass') ) + if $cgi->param('taxclass'); + +} + +$where .= ' AND pkgnum != 0' if $cgi->param('nottax'); + +$where .= ' AND pkgnum = 0' if $cgi->param('istax'); + +$where .= " AND tax = 'Y'" if $cgi->param('cust_tax'); + +my $count_query; +if ( $cgi->param('pkg_tax') ) { + + $count_query = + "SELECT COUNT(*), SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + "; + + $where .= " AND ( + ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) + OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) + ) + AND ( tax != 'Y' OR tax IS NULL ) + "; + +} else { + + $count_query = + "SELECT COUNT(*), SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)"; + +} +$count_query .= " FROM cust_bill_pkg $join_cust $join_pkg $where"; + +my $query = { + 'table' => 'cust_bill_pkg', + 'addl_from' => "$join_cust $join_pkg", + 'hashref' => {}, + 'select' => join(', ', + 'cust_bill_pkg.*', + 'cust_bill._date', + 'part_pkg.pkg', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $where, +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +</%init> diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html new file mode 100755 index 000000000..e4975c8de --- /dev/null +++ b/httemplate/search/cust_credit.html @@ -0,0 +1,104 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'credits', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total credited', ], + #'redirect' => $link, + 'header' => [ 'Amount', + 'Date', + FS::UI::Web::cust_header(), + 'By', + 'Reason' + ], + 'fields' => [ + #'crednum', + sub { sprintf('$%.2f', shift->amount ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + \&FS::UI::Web::cust_fields, + 'otaker', + 'reason', + ], + #'align' => 'rrrllll', + 'align' => 'rr'.FS::UI::Web::cust_aligns().'ll', + 'links' => [ + '', + '', + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + '', + '', + ], + 'color' => [ + '', + '', + FS::UI::Web::cust_colors(), + '', + '', + ], + 'style' => [ + '', + '', + FS::UI::Web::cust_styles(), + '', + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $title = 'Credit Search Results'; +#my( $count_query, $sql_query ); + +my @search = (); + +if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { + push @search, "cust_credit.otaker = '$1'"; +} + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "_date >= $beginning ", + "_date <= $ending"; + +push @search, FS::UI::Web::parse_lt_gt($cgi, 'amount' ); + +#here is the agent virtualization +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = 'WHERE '. join(' AND ', @search); + +my $count_query = 'SELECT COUNT(*), SUM(amount) '. + 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '. + $where; + +my $sql_query = { + 'table' => 'cust_credit', + 'select' => join(', ', + 'cust_credit.*', + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => $where, + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', +}; + + my $clink = sub { + my $cust_bill = shift; + $cust_bill->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; + }; + +</%init> diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html new file mode 100644 index 000000000..d55b5c6d2 --- /dev/null +++ b/httemplate/search/cust_event.html @@ -0,0 +1,285 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'html_init' => $html_init, + 'menubar' => $menubar, + 'name' => 'billing events', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Event', + 'Date', + 'Status', + 'Trigger', + #'Inv #', 'Inv Date', 'Cust #', + #'Invoice', + + FS::UI::Web::cust_header(), #'cust_main_custnum', + ], + 'fields' => [ + 'event', + sub { time2str("%b %d %Y %T", $_[0]->_date) }, + $status_sub, + $trigger_sub, + #sub { + # #my $cust_event = shift; + # 'Invoice #'. $_[0]->invnum. + # ' ('. + # time2str("%D", $_[0]->cust_bill_date). + # ')'; + # }, + \&FS::UI::Web::cust_fields, + ], + 'align' => 'lrll'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + '', + $trigger_link, + #sub { + # my $part_event = shift; + # #XXX + # my $template = $part_event->templatename; + # $template .= '-' if $template; + # [ "${p}view/cust_bill.cgi?$template", 'invnum']; + #}, + + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + #'', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + #'', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%once> + +my $status_sub = sub { + my $cust_event = shift; + + my $status = $cust_event->status; + $status .= ': '.$cust_event->statustext + if $cust_event->statustext; + + my $part_event = $cust_event->part_event; + + if ( $part_event->eventtable eq 'cust_bill' && $part_event->templatename ) { + my $alt_templatename = $part_event->templatename; + my $alt_link = "$alt_templatename-". $cust_event->tablenum; + + my $conf = new FS::Conf; + my $cust_bill = $cust_event->cust_X; + + $status .= qq{ + ( <A HREF="${p}view/cust_bill.cgi?$alt_link">view</A> + | <A HREF="${p}view/cust_bill-pdf.cgi?$alt_link.pdf">view + typeset</A> + | <A HREF="${p}misc/print-invoice.cgi?$alt_link">re-print</A> + }; + + if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { + $status .= qq{ + | <A HREF="${p}misc/email-invoice.cgi?$alt_link">re-email</A> + }; + } + + if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { + $status .= qq{ + | <A HREF="${p}misc/fax-invoice.cgi?$alt_link">re-fax</A> + } + } + + $status .= ' ) '; + + } + + $status; +}; + +my $trigger_sub = sub { + my $cust_event = shift; + my $eventtable = $cust_event->eventtable; + my $label = FS::part_event->eventtable_labels->{$eventtable}; + #if ( $eventtable eq 'cust_pkg' || $eventtable eq 'cust_bill' ) { + "$label #". $cust_event->tablenum; + #} else { + # $label; + #} +}; + +my $trigger_link = sub { + my $cust_event = shift; + my $eventtable = $cust_event->eventtable; + if ( $eventtable eq 'cust_pkg' ) { + my $custnum = $cust_event->cust_main_custnum; + [ "${p}view/cust_main.cgi?$custnum#cust_pkg", 'tablenum' ]; + } else { + [ "${p}view/$eventtable.cgi?", 'tablenum' ]; + } +}; + +</%once> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Billing event reports') + or $curuser->access_right('View customer billing events') + && ( $cgi->param('custnum') =~ /^(\d+)$/ + || $cgi->param('invnum') =~ /^(\d+)$/ + || $cgi->param('pkgnum') =~ /^(\d+)$/ + ); + + +my $title = $cgi->param('failed') + ? 'Failed billing events' + : 'Billing events'; + +my @search = (); + +if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "cust_main.agentnum = $1"; + #my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + #die "unknown agentnum $1" unless $agent; +} + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @search, "cust_event._date >= $beginning", + "cust_event._date <= $ending"; + +if ( $cgi->param('failed') ) { + push @search, "statustext != ''", + "statustext IS NOT NULL", + "statustext != 'N/A'"; +} + +#if ( $cgi->param('part_event.payby') =~ /^(\w+)$/ ) { +# push @search, "part_event.payby = '$1'"; +#} + +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @search, "cust_main.custnum = '$1'"; +} +if ( $cgi->param('invnum') =~ /^(\d+)$/ ) { + push @search, "part_event.eventtable = 'cust_bill'", + "tablenum = '$1'"; +} +if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) { + push @search, "part_event.eventtable = 'cust_pkg'", + "tablenum = '$1'"; +} + +#here is the agent virtualization +push @search, $curuser->agentnums_sql( 'table' => 'cust_main' ); + +my $where = 'WHERE '. join(' AND ', @search ); + +my $join = " + JOIN part_event USING ( eventpart ) + LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = invnum ) + LEFT JOIN cust_pkg ON ( eventtable = 'cust_pkg' AND tablenum = pkgnum ) + LEFT JOIN cust_main ON ( ( eventtable = 'cust_main' AND tablenum = cust_main.custnum ) + OR ( eventtable = 'cust_bill' AND cust_bill.custnum = cust_main.custnum ) + OR ( eventtable = 'cust_pkg' AND cust_pkg.custnum = cust_main.custnum ) + ) +"; + #'LEFT JOIN cust_main USING ( custnum ) '; + +my $sql_query = { + 'table' => 'cust_event', + 'select' => join(', ', + 'cust_event.*', + 'part_event.*', + #'cust_bill.custnum', + #'cust_bill._date AS cust_bill_date', + 'cust_main.custnum AS cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$where ORDER BY _date ASC", + 'addl_from' => $join, +}; + +my $count_sql = "SELECT COUNT(*) FROM cust_event $join $where"; + +my $conf = new FS::Conf; + +my $failed = $cgi->param('failed'); + +my $html_init = join("\n", map { + ( my $action = $_ ) =~ s/_$//; + include('/elements/progress-init.html', + $_.'form', + [ 'action', 'beginning', 'ending', 'failed' ], + "../misc/${_}events.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $_, #key + ), + qq!<FORM NAME="${_}form">!, + qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though + qq!<INPUT TYPE="hidden" NAME="beginning" VALUE="$beginning">!, + qq!<INPUT TYPE="hidden" NAME="ending" VALUE="$ending">!, + qq!<INPUT TYPE="hidden" NAME="failed" VALUE="$failed">!, + qq!</FORM>! +} qw( print_ email_ fax_ ) ). + +'<SCRIPT TYPE="text/javascript"> + +function confirm_print_process() { + if ( ! confirm("Are you sure you want to reprint these invoices?") ) { + return; + } + print_process(); +} +function confirm_email_process() { + if ( ! confirm("Are you sure you want to re-email these invoices?") ) { + return; + } + email_process(); +} +function confirm_fax_process() { + if ( ! confirm("Are you sure you want to re-fax these invoices?") ) { + return; + } + fax_process(); +} + +</SCRIPT>'; + +my $menubar = []; + +if ( $curuser->access_right('Resend invoices') ) { + + push @$menubar, 'Re-print these events' => + "javascript:confirm_print_process()", + 'Re-email these events' => + "javascript:confirm_email_process()", + ; + + push @$menubar, 'Re-fax these events' => + "javascript:confirm_fax_process()" + if $conf->exists('hylafax'); + +} + +my $link_cust = sub { + my $cust_event = shift; + $cust_event->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'cust_main_custnum' ] + : ''; +}; + +</%init> diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi new file mode 100755 index 000000000..0c252e44b --- /dev/null +++ b/httemplate/search/cust_main-otaker.cgi @@ -0,0 +1,31 @@ +<% include('/elements/header.html', 'Customer Search' ) %> + +<FORM ACTION="cust_main.cgi" METHOD="GET"> + +Search for <B>Order taker</B>: + <INPUT TYPE="hidden" NAME="otaker_on" VALUE="TRUE"> +% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") +% or die dbh->errstr; +% $sth->execute() or die $sth->errstr; +% #my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; +% + +<SELECT NAME="otaker"> +% my $otaker; while ( $otaker = $sth->fetchrow_arrayref ) { + + <OPTION><% $otaker->[0] %> +% } + +</SELECT> + +<P><INPUT TYPE="submit" VALUE="Search"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html new file mode 100644 index 000000000..56df924bc --- /dev/null +++ b/httemplate/search/cust_main-zip.html @@ -0,0 +1,99 @@ +<% include( 'elements/search.html', + 'title' => 'Zip code Search Results', + 'name' => 'zip codes', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ 'Zip code', 'Customers', ], + #'fields' => [ 'zip', 'num_cust', ], + 'links' => [ '', sub { 'somewhere'; } ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List zip codes'); + +# XXX link to customers + +my @where = (); + +# select status + +if ( $cgi->param('status') =~ /^(prospect|uncancel|active|susp|cancel)$/ ) { + my $method = $1.'_sql'; + push @where, FS::cust_main->$method(); +} + +# select agent +# XXX this needs to be virtualized by agent too (like lots of stuff) + +my $agentnum = ''; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + push @where, "cust_main.agentnum = $agentnum"; +} +my $where = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; + +# bill zip vs ship zip + +sub fieldorempty { + my $field = shift; + "CASE WHEN $field IS NULL THEN '' ELSE $field END"; +} + +sub strip_plus4 { + my $field = shift; + "CASE WHEN $field is NULL + THEN '' + ELSE CASE WHEN $field LIKE '_____-____' + THEN SUBSTRING($field FROM 1 FOR 5) + ELSE $field + END + END"; +} + +my( $zip, $czip); +if ( $cgi->param('column') eq 'ship_zip' ) { + + my $casewhen_noship = + "CASE WHEN ( ship_last IS NULL OR ship_last = '' ) THEN "; + + $czip = "$casewhen_noship zip ELSE ship_zip END"; + + if ( $cgi->param('ignore_plus4') ) { + $zip = $casewhen_noship. strip_plus4('zip'). + " ELSE ". strip_plus4('ship_zip'). ' END'; + + } else { + $zip = $casewhen_noship. fieldorempty('zip'). + " ELSE ". fieldorempty('ship_zip'). ' END'; + } + +} else { + + $czip = 'zip'; + + if ( $cgi->param('ignore_plus4') ) { + $zip = strip_plus4('zip'); + } else { + $zip = fieldorempty('zip'); + } + +} + +# construct the queries and send 'em off + +my $sql_query = + "SELECT $zip AS zipcode, + COUNT(*) AS num_cust + FROM cust_main + $where + GROUP BY zipcode + ORDER BY num_cust DESC + "; + +my $count_sql = "select count(distinct $czip) from cust_main $where"; + +# XXX should link... + +</%init> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi new file mode 100755 index 000000000..1ddafae0b --- /dev/null +++ b/httemplate/search/cust_main.cgi @@ -0,0 +1,727 @@ +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List customers'); +% +%my $conf = new FS::Conf; +%my $maxrecords = $conf->config('maxsearchrecordsperpage'); +% +%#my $cache; +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join svc_acct using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%#my $monsterjoin = <<END; +%#cust_main left outer join ( +%# ( cust_pkg left outer join part_pkg using(pkgpart) +%# ) left outer join ( +%# ( +%# ( +%# ( cust_svc left outer join part_svc using (svcpart) +%# ) left outer join ( +%# svc_acct left outer join ( +%# select svcnum, domain, catchall from svc_domain +%# ) as svc_acct_domsvc ( +%# svc_acct_svcnum, svc_acct_domain, svc_acct_catchall +%# ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum +%# ) using (svcnum) +%# ) left outer join svc_domain using(svcnum) +%# ) left outer join svc_forward using(svcnum) +%# ) using (pkgnum) +%#) using (custnum) +%#END +% +%my $limit = ''; +%$limit .= "LIMIT $maxrecords" if $maxrecords; +% +%my $offset = $cgi->param('offset') || 0; +%$limit .= " OFFSET $offset" if $offset; +% +%my $total = 0; +% +%my(@cust_main, $sortby, $orderby); +%my @select = (); +%my @addl_headers = (); +%my @addl_cols = (); +%if ( $cgi->param('browse') +% || $cgi->param('otaker_on') +% || $cgi->param('agentnum_on') +%) { +% +% my %search = (); +% +% if ( $cgi->param('browse') ) { +% my $query = $cgi->param('browse'); +% if ( $query eq 'custnum' ) { +% $sortby=\*custnum_sort; +% $orderby = "ORDER BY custnum"; +% } elsif ( $query eq 'last' ) { +% $sortby=\*last_sort; +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; +% } elsif ( $query eq 'company' ) { +% $sortby=\*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% } elsif ( $query eq 'tickets' ) { +% $sortby = \*tickets_sort; +% $orderby = "ORDER BY tickets DESC"; +% push @select, FS::TicketSystem->sql_num_customer_tickets. " as tickets"; +% push @addl_headers, 'Tickets'; +% push @addl_cols, 'tickets'; +% } else { +% die "unknown browse field $query"; +% } +% } else { +% $sortby = \*last_sort; #?? +% $orderby = "ORDER BY LOWER(last || ' ' || first)"; #?? +% } +% +% if ( $cgi->param('otaker_on') ) { +% die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +% $cgi->param('otaker') =~ /^(\w{1,32})$/ or errorpage("Illegal otaker"); +% $search{otaker} = $1; +% } elsif ( $cgi->param('agentnum_on') ) { +% $cgi->param('agentnum') =~ /^(\d+)$/ or errorpage("Illegal agentnum"); +% $search{agentnum} = $1; +%# } else { +%# die "unknown query..."; +% } +% +% my @qual = (); +% +% my $ncancelled = ''; +% +% if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') ) +% ) { +% #grep { $_->ncancelled_pkgs || ! $_->all_pkgs } +% push @qual, FS::cust_main->uncancel_sql; +% +% } +% +% push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled'); +% push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect'); +% push @qual, FS::cust_main->active_sql if $cgi->param('active'); +% push @qual, FS::cust_main->inactive_sql if $cgi->param('inactive'); +% push @qual, FS::cust_main->susp_sql if $cgi->param('suspended'); +% +% #EWWWWWW +% my $qual = join(' AND ', +% map { "$_ = ". dbh->quote($search{$_}) } keys %search ); +% +% my $addl_qual = join(' AND ', @qual); +% +% #here is the agent virtualization +% $addl_qual .= ( $addl_qual ? ' AND ' : '' ). +% $FS::CurrentUser::CurrentUser->agentnums_sql; +% +% if ( $addl_qual ) { +% $qual .= ' AND ' if $qual; +% $qual .= $addl_qual; +% } +% +% $qual = " WHERE $qual" if $qual; +% my $statement = "SELECT COUNT(*) FROM cust_main $qual"; +% my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; +% $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; +% +% $total = $sth->fetchrow_arrayref->[0]; +% +% if ( $addl_qual ) { +% if ( %search ) { +% $addl_qual = " AND $addl_qual"; +% } else { +% $addl_qual = " WHERE $addl_qual"; +% } +% } +% +% my $select; +% if ( @select ) { +% $select = 'cust_main.*, '. join (', ', @select); +% } else { +% $select = '*'; +% } +% +% @cust_main = qsearch('cust_main', \%search, $select, +% "$addl_qual $orderby $limit" ); +% +%# foreach my $cust_main ( @just_cust_main ) { +%# +%# my @one_cust_main; +%# $FS::Record::DEBUG=1; +%# ( $cache, @one_cust_main ) = jsearch( +%# "$monsterjoin", +%# { 'custnum' => $cust_main->custnum }, +%# '', +%# '', +%# 'cust_main', +%# 'custnum', +%# ); +%# push @cust_main, @one_cust_main; +%# } +% +%} else { +% @cust_main=(); +% $sortby = \*last_sort; +% +% push @cust_main, @{&custnumsearch} +% if $cgi->param('custnum_on') && $cgi->param('custnum_text'); +% push @cust_main, @{&cardsearch} +% if $cgi->param('card_on') && $cgi->param('card'); +% push @cust_main, @{&lastsearch} +% if $cgi->param('last_on') && $cgi->param('last_text'); +% push @cust_main, @{&companysearch} +% if $cgi->param('company_on') && $cgi->param('company_text'); +% push @cust_main, @{&address2search} +% if $cgi->param('address2_on') && $cgi->param('address2_text'); +% push @cust_main, @{&phonesearch} +% if $cgi->param('phone_on') && $cgi->param('phone_text'); +% push @cust_main, @{&referralsearch} +% if $cgi->param('referral_custnum'); +% +% if ( $cgi->param('company_on') && $cgi->param('company_text') ) { +% $sortby = \*company_sort; +% push @cust_main, @{&companysearch}; +% } +% +% if ( $cgi->param('search_cust') ) { +% $sortby = \*company_sort; +% $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; +% push @cust_main, smart_search( 'search' => $cgi->param('search_cust') ); +% } +% +% @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main +% if ! $cgi->param('cancelled') +% && ( +% $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') ) +% ); +% +% my %saw = (); +% @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; +%} +% +%my %all_pkgs; +%if ( $conf->exists('hidecancelledpackages' ) ) { +% %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main; +%} else { +% %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main; +%} +%#%all_pkgs = (); +% +%if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { +% if ( $cgi->param('quickpay') eq 'yes' ) { +% print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum); +% } else { +% print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum); +% } +% #exit; +%} elsif ( scalar(@cust_main) == 0 ) { +% + +<!-- mason kludge --> +% +% errorpage("No matching customers found!"); +%} else { +% + +<% include('/elements/header.html', "Customer Search Results", '' ) %> +% $total ||= scalar(@cust_main); + + + <% $total %> matching customers found + +% my $pager = include( '/elements/pager.html', +% 'offset' => $offset, +% 'num_rows' => scalar(@cust_main), +% 'total' => $total, +% 'maxrecords' => $maxrecords, +% ); +% +% unless ( $cgi->param('cancelled') ) { +% if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +% || ( $conf->exists('hidecancelledcustomers') +% && ! $cgi->param('showcancelledcustomers') +% ) +% ) { +% $cgi->param('showcancelledcustomers', 1); +% $cgi->param('offset', 0); +% print qq!( <a href="!. $cgi->self_url. qq!">show!; +% } else { +% $cgi->param('showcancelledcustomers', 0); +% $cgi->param('offset', 0); +% print qq!( <a href="!. $cgi->self_url. qq!">hide!; +% } +% print ' cancelled customers</a> )'; +% } +% +% if ( $cgi->param('referral_custnum') ) { +% $cgi->param('referral_custnum') =~ /^(\d+)$/ +% or errorpage("Illegal referral_custnum"); +% my $referral_custnum = $1; +% my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } ); +% print '<FORM METHOD="GET">'. +% qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!. +% 'referrals of <A HREF="'. popurl(2). +% "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ". +% ( $cust_main->company +% || $cust_main->last. ', '. $cust_main->first ). +% '</A>'; +% print "\n",<<END; +% <SCRIPT> +% function changed(what) { +% what.form.submit(); +% } +% </SCRIPT> +%END +% print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">'; +% my $max = 8; #config file +% $cgi->param('referral_depth') =~ /^(\d*)$/ +% or errorpage("Illegal referral_depth"); +% my $referral_depth = $1; +% +% foreach my $depth ( 1 .. $max ) { +% print '<OPTION', +% ' SELECTED'x($depth == $referral_depth), +% ">$depth"; +% } +% print "</SELECT> levels deep". +% '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'. +% '</FORM>'; +% } +% +% my @custom_priorities = (); +% if ( $conf->config('ticket_system-custom_priority_field') +% && @{[ $conf->config('ticket_system-custom_priority_field-values') ]} ) { +% @custom_priorities = +% $conf->config('ticket_system-custom_priority_field-values'); +% } +% +% print "<BR><BR>". $pager. include('/elements/table-grid.html'). <<END; +% <TR> +% <TH CLASS="grid" BGCOLOR="#cccccc">#</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">Status</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">(bill) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +% +%if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">(service) name</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc">company</TH> +%END +%} +% +%foreach my $addl_header ( @addl_headers ) { +% print '<TH CLASS="grid" BGCOLOR="#cccccc">'. "$addl_header</TH>"; +%} +% +%print <<END; +% <TH CLASS="grid" BGCOLOR="#cccccc">Packages</TH> +% <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Services</TH> +% </TR> +%END +% +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% my(%saw,$cust_main); +% foreach $cust_main ( +% sort $sortby grep(!$saw{$_->custnum}++, @cust_main) +% ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my($custnum,$last,$first,$company)=( +% $cust_main->custnum, +% $cust_main->getfield('last'), +% $cust_main->getfield('first'), +% $cust_main->company, +% ); +% +% my(@lol_cust_svc); +% my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} ); +% foreach ( @{$all_pkgs{$custnum}} ) { +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my @cust_svc = $_->cust_svc; +% push @lol_cust_svc, \@cust_svc; +% $rowspan += scalar(@cust_svc) || 1; +% } +% +% #my($rowspan) = scalar(@{$all_pkgs{$custnum}}); +% my $view; +% if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) { +% $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum; +% } else { +% $view = $p. 'view/cust_main.cgi?'. $custnum; +% } +% my $pcompany = $company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% +% my $status = $cust_main->status; +% my $statuscol = $cust_main->statuscolor; + + <TR> + <TD CLASS="grid" ALIGN="right" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% $custnum %></FONT></A></TD> + <TD CLASS="grid" ALIGN="center" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><FONT SIZE="-1" COLOR="#<% $statuscol %>"><B><% ucfirst($status) %></B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$last, $first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pcompany %></TD> +% +% if ( defined dbdef->table('cust_main')->column('ship_last') ) { +% my($ship_last,$ship_first,$ship_company)=( +% $cust_main->ship_last || $cust_main->getfield('last'), +% $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, +% $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, +% ); +% my $pship_company = $ship_company +% ? qq!<A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A>! +% : '<FONT SIZE=-1> </FONT>'; +% + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><A HREF="<% $view %>"><FONT SIZE=-1><% "$ship_last, $ship_first" %></FONT></A></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %>><% $pship_company %></A></TD> +% } +% +% foreach my $addl_col ( @addl_cols ) { +% if ( $addl_col eq 'tickets' ) { +% if ( @custom_priorities ) { + + + <TD CLASS="inv" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + + <TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0> +% foreach my $priority ( @custom_priorities, '' ) { +% +% my $num = +% FS::TicketSystem->num_customer_tickets($custnum,$priority); +% my $ahref = ''; +% $ahref= '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum,$priority). +% '">' +% if $num; +% + + + <TR> + <TD ALIGN=right> + <FONT SIZE=-1><% $ahref.$num %></A></FONT> + </TD> + <TD ALIGN=left> + <FONT SIZE=-1><% $ahref %><% $priority || '<i>(none)</i>' %></A></FONT> + </TD> + </TR> +% } + + + <TR> + <TH ALIGN=right STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> +% } +% +% my $ahref = ''; +% $ahref = '<A HREF="'. +% FS::TicketSystem->href_customer_tickets($custnum). +% '">' +% if $cust_main->get($addl_col); +% + + + <% $ahref %><% $cust_main->get($addl_col) %></A> +% if ( @custom_priorities ) { + + + </FONT></TH> + <TH ALIGN=left STYLE="border-top: dashed 1px black"> + <FONT SIZE=-1><% ${ahref} %>Total</A><FONT> + </TH> + </TR> + </TABLE> +% } + + + </FONT></TD> +% } else { + + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN=<% $rowspan || 1 %> ALIGN=right><FONT SIZE=-1> + <% $cust_main->get($addl_col) %> + </FONT></TD> +% +% } +% } +% +% my($n1)=''; +% foreach ( @{$all_pkgs{$custnum}} ) { +% my $pkgnum = $_->pkgnum; +%# my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } ); +% my $part_pkg = $_->part_pkg; +% +% my $pkg = $part_pkg->pkg; +% my $comment = $part_pkg->comment; +% my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum"; +% my @cust_svc = @{shift @lol_cust_svc}; +% #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +% my $rowspan = scalar(@cust_svc) || 1; +% +% print $n1, qq!<TD CLASS="grid" BGCOLOR="$bgcolor" ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!; +% +% my($n2)=''; +% foreach my $cust_svc ( @cust_svc ) { +% my($label, $value, $svcdb) = $cust_svc->label; +% my($svcnum) = $cust_svc->svcnum; +% my($sview) = $p.'view'; +% print $n2, +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !. +% qq!<TD CLASS="grid" BGCOLOR="$bgcolor" >!. FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) . qq!</TD> !; +% $n2="</TR><TR>"; +% } +% +% unless ( @cust_svc ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=2> </TD>!; +% } +% +% #print qq!</TR><TR>\n!; +% $n1="</TR><TR>"; +% } +% +% unless ( @{$all_pkgs{$custnum}} ) { +% print qq!<TD CLASS="grid" BGCOLOR="$bgcolor" COLSPAN=3> </TD>!; +% } +% +% print "</TR>"; +% } +% +% + + + </TABLE><% $pager %> + + <% include('/elements/footer.html') %> +% } +% +%#undef $cache; #does this help? +% +%# +% +%sub last_sort { +% lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first); +%} +% +%sub company_sort { +% return -1 if $a->company && ! $b->company; +% return 1 if ! $a->company && $b->company; +% lc($a->company) cmp lc($b->company) +% || lc($a->getfield('last')) cmp lc($b->getfield('last')) +% || lc($a->first) cmp lc($b->first);; +%} +% +%sub custnum_sort { +% $a->getfield('custnum') <=> $b->getfield('custnum'); +%} +% +%sub tickets_sort { +% $b->getfield('tickets') <=> $a->getfield('tickets'); +%} +% +%sub custnumsearch { +% +% my $custnum = $cgi->param('custnum_text'); +% $custnum =~ s/\D//g; +% $custnum =~ /^(\d{1,23})$/ or errorpage("Illegal customer number"); +% $custnum = $1; +% +% [ qsearchs('cust_main', { 'custnum' => $custnum } ) ]; +%} +% +%sub cardsearch { +% +% my($card)=$cgi->param('card'); +% $card =~ s/\D//g; +% $card =~ /^(\d{13,16})$/ or errorpage("Illegal card number"); +% my($payinfo)=$1; +% +% [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}), +% qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'}) +% ]; +%} +% +%sub referralsearch { +% $cgi->param('referral_custnum') =~ /^(\d+)$/ +% or errorpage("Illegal referral_custnum"); +% my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } ) +% or errorpage("Customer $1 not found"); +% my $depth; +% if ( $cgi->param('referral_depth') ) { +% $cgi->param('referral_depth') =~ /^(\d+)$/ +% or errorpage("Illegal referral_depth"); +% $depth = $1; +% } else { +% $depth = 1; +% } +% [ $cust_main->referral_cust_main($depth) ]; +%} +% +%sub lastsearch { +% my(%last_type); +% my @cust_main; +% foreach ( $cgi->param('last_type') ) { +% $last_type{$_}++; +% } +% +% $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/ +% or errorpage("Illegal last name"); +% my($last)=$1; +% +% if ( $last_type{'Exact'} || $last_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => $last } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => $last } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $last_type{'Substring'} || $last_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_last' => { 'op' => 'ILIKE', +% 'value' => "%$last%" } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $last_type{'Fuzzy'} || $last_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'last' => $last } ); +% } +% +% #if ($last_type{'Sound-alike'}) { +% #} +% +% \@cust_main; +%} +% +%sub companysearch { +% +% my(%company_type); +% my @cust_main; +% foreach ( $cgi->param('company_type') ) { +% $company_type{$_}++ +% }; +% +% $cgi->param('company_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or errorpage("Illegal company"); +% my $company = $1; +% +% if ( $company_type{'Exact'} || $company_type{'Fuzzy'} ) { +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => $company } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => $company } } ) +% if defined dbdef->table('cust_main')->column('ship_last'); +% } +% +% if ( $company_type{'Substring'} || $company_type{'All'} ) { +% +% push @cust_main, qsearch( 'cust_main', +% { 'company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } } ); +% +% push @cust_main, qsearch( 'cust_main', +% { 'ship_company' => { 'op' => 'ILIKE', +% 'value' => "%$company%" } }) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% } +% +% if ( $company_type{'Fuzzy'} || $company_type{'All'} ) { +% push @cust_main, FS::cust_main->fuzzy_search( { 'company' => $company } ); +% } +% +% if ($company_type{'Sound-alike'}) { +% } +% +% \@cust_main; +%} +% +%sub address2search { +% my @cust_main; +% +% $cgi->param('address2_text') =~ +% /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/ +% or errorpage("Illegal address2"); +% my $address2 = $1; +% +% push @cust_main, qsearch( 'cust_main', +% { 'address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ); +% push @cust_main, qsearch( 'cust_main', +% { 'ship_address2' => { 'op' => 'ILIKE', +% 'value' => $address2 } } ); +% +% \@cust_main; +%} +% +%sub phonesearch { +% my @cust_main; +% +% my $phone = $cgi->param('phone_text'); +% +% #(no longer really) false laziness with Record::ut_phonen +% #only works with US/CA numbers... +% $phone =~ s/\D//g; +% if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) { +% $phone = "$1-$2-$3"; +% $phone .= " x$4" if $4; +% } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) { +% $phone = "$1-$2"; +% } elsif ( $phone =~ /^(\d{3,4})$/ ) { +% $phone = $1; +% } else { +% errorpage(gettext('illegal_phone'). ": $phone"); +% } +% +% my @fields = qw(daytime night fax); +% push @fields, qw(ship_daytime ship_night ship_fax) +% if defined dbdef->table('cust_main')->column('ship_last'); +% +% for my $field ( @fields ) { +% push @cust_main, qsearch ( 'cust_main', +% { $field => { 'op' => 'LIKE', +% 'value' => "%$phone%" } } ); +% } +% +% \@cust_main; +%} diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html new file mode 100755 index 000000000..c050c5bd1 --- /dev/null +++ b/httemplate/search/cust_main.html @@ -0,0 +1,172 @@ +<% include( 'elements/search.html', + 'title' => 'Customer Search Results', + 'name' => 'customers', + 'query' => $sql_query, + 'count_query' => $count_query, + 'header' => [ FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), + @extra_headers, + ], + 'fields' => [ + \&FS::UI::Web::cust_fields, + @extra_fields, + ], + 'color' => [ FS::UI::Web::cust_colors(), + map '', @extra_fields + ], + 'style' => [ FS::UI::Web::cust_styles(), + map '', @extra_fields + ], + 'align' => [ FS::UI::Web::cust_aligns(), + map '', @extra_fields + ], + 'links' => [ ( map { $_ ne 'Cust. Status' ? $link : '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + map '', @extra_fields + ], + ) +%> +<%once> + +my $link = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +</%once> +<%init> + +die "access denied" + unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') && + $FS::CurrentUser::CurrentUser->access_right('List packages') + ); + +my $dbh = dbh; +my $conf = new FS::Conf; +my $countrydefault = $conf->config('countrydefault'); + +my($query) = $cgi->keywords; + +my @where = (); + +## +# parse agent +## + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { + push @where, + "agentnum = $1"; +} + +## +# parse cancelled package checkbox +## + +my $pkgwhere = ""; + +$pkgwhere .= "AND (cancel = 0 or cancel is null)" + unless $cgi->param('cancelled_pkgs'); + +my $orderby; + +## +# dates +## + +foreach my $field (qw( signupdate )) { + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295; + + push @where, + "cust_main.$field IS NOT NULL", + "cust_main.$field >= $beginning", + "cust_main.$field <= $ending"; + + $orderby ||= "ORDER BY cust_main.$field"; + +} + +### +# payby +### + +my @payby = grep /^([A-Z]{4})$/, $cgi->param('payby'); +if ( @payby ) { + push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )'; +} + +## +# amounts +## + +my $balance_sql = FS::cust_main->balance_sql(); + +push @where, map { s/current_balance/$balance_sql/; $_ } + FS::UI::Web::parse_lt_gt($cgi, 'current_balance'); + +## +# setup queries, subs, etc. for the search +## + +$orderby ||= 'ORDER BY custnum'; + +# here is the agent virtualization +push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; + +my $addl_from = 'LEFT JOIN cust_pkg USING ( custnum ) '; + +my $count_query = "SELECT COUNT(*) FROM cust_main $extra_sql"; + +my $select = join(', ', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields($cgi->param('cust_fields')), + ); + +my (@extra_headers) = (); +my (@extra_fields) = (); + +if ($cgi->param('flattened_pkgs')) { + + if ($dbh->{Driver}->{Name} eq 'Pg') { + + $select .= ", array_to_string(array(select pkg from cust_pkg left join part_pkg using ( pkgpart ) where cust_main.custnum = cust_pkg.custnum $pkgwhere),'|') as magic"; + + }elsif ($dbh->{Driver}->{Name} =~ /^mysql/i) { + $select .= ", GROUP_CONCAT(pkg SEPARATOR '|') as magic"; + $addl_from .= " LEFT JOIN part_pkg using ( pkgpart )"; + }else{ + warn "warning: unknown database type ". $dbh->{Driver}->{Name}. + "omitting packing information from report."; + } + + my $header_query = "SELECT COUNT(cust_pkg.custnum = cust_main.custnum) AS count FROM cust_main $addl_from $extra_sql $pkgwhere group by cust_main.custnum order by count desc limit 1"; + + my $sth = dbh->prepare($header_query) or die dbh->errstr; + $sth->execute() or die $sth->errstr; + my $headerrow = $sth->fetchrow_arrayref; + my $headercount = $headerrow ? $headerrow->[0] : 0; + while($headercount) { + unshift @extra_headers, "Package ". $headercount; + unshift @extra_fields, eval q!sub {my $c = shift; + my @a = split '\|', $c->magic; + my $p = $a[!.--$headercount. q!]; + $p; + };!; + } + +} + +my $sql_query = { + 'table' => 'cust_main', + 'select' => $select, + 'hashref' => {}, + 'extra_sql' => "$extra_sql $orderby", +}; + + +</%init> diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi new file mode 100755 index 000000000..e5465aee8 --- /dev/null +++ b/httemplate/search/cust_pay.cgi @@ -0,0 +1,247 @@ +<% include( 'elements/search.html', + 'title' => $title, + 'name' => 'payments', + 'query' => $sql_query, + 'count_query' => $count_query, + 'count_addl' => [ '$%.2f total paid', ], + 'header' => [ 'Payment', + 'Amount', + 'Date', + 'By', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + sub { + my $cust_pay = shift; + if ( $cust_pay->payby eq 'CARD' ) { + 'Card #'. $cust_pay->paymask; + } elsif ( $cust_pay->payby eq 'CHEK' ) { + 'E-check acct#'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'BILL' ) { + 'Check #'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'PREP' ) { + 'Prepaid card #'. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'CASH' ) { + 'Cash '. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'WEST' ) { + 'Western Union'; #. $cust_pay->payinfo; + } elsif ( $cust_pay->payby eq 'MCRD' ) { + 'Manual credit card'; #. $cust_pay->payinfo; + } else { + $cust_pay->payby. ' '. $cust_pay->payinfo; + } + }, + sub { sprintf('$%.2f', shift->paid ) }, + sub { time2str('%b %d %Y', shift->_date ) }, + sub { my $o = shift->otaker; + $o = 'auto billing' if $o eq 'fs_daily'; + $o = 'customer self-service' if $o eq 'fs_selfservice'; + $o; + }, + \&FS::UI::Web::cust_fields, + ], + #'align' => 'lrrrll', + 'align' => 'rrrc'.FS::UI::Web::cust_aligns(), + 'links' => [ + $link, + $link, + $link, + '', + ( map { $_ ne 'Cust. Status' ? $cust_link : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $title = 'Payment Search Results'; +my( $count_query, $sql_query ); +if ( $cgi->param('magic') ) { + + my @search = (); + my $orderby; + if ( $cgi->param('magic') eq '_date' ) { + + + if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @search, "agentnum = $1"; # $search{'agentnum'} = $1; + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "unknown agentnum $1" unless $agent; + $title = $agent->agent. " $title"; + } + + if ( $cgi->param('payby') ) { + $cgi->param('payby') =~ + /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ + or die "illegal payby ". $cgi->param('payby'); + push @search, "cust_pay.payby = '$1'"; + if ( $3 ) { + + my $cardtype = $3; + + my $search; + if ( $cardtype eq 'VisaMC' ) { + #avoid posix regexes for portability + $search = + " ( ( substring(cust_pay.payinfo from 1 for 1) = '4' ". + " AND substring(cust_pay.payinfo from 1 for 4) != '4936' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49030[2-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49033[5-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49110[1-2]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49117[4-9]' ". + " AND substring(cust_pay.payinfo from 1 for 6) ". + " NOT SIMILAR TO '49118[1-2]' ". + " )". + " OR substring(cust_pay.payinfo from 1 for 2) = '51' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '52' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '53' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '54' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '55' ". + " OR substring(cust_pay.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US + " ) "; + } elsif ( $cardtype eq 'Amex' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 2 ) = '34' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ". + " ) "; + } elsif ( $cardtype eq 'Discover' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 4 ) = '6011' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '65' ". + " OR substring(cust_pay.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN + " ) "; + } elsif ( $cardtype eq 'Maestro' ) { + $search = + " ( substring(cust_pay.payinfo from 1 for 2 ) = '63' ". + " OR substring(cust_pay.payinfo from 1 for 2 ) = '67' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ". + " OR substring(cust_pay.payinfo from 1 for 4 ) = '4936' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49030[2-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49033[5-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49110[1-2]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49117[4-9]' ". + " OR substring(cust_pay.payinfo from 1 for 6 ) ". + " SIMILAR TO '49118[1-2]' ". + " ) "; + } else { + die "unknown card type $cardtype"; + } + + my $masksearch = $search; + $masksearch =~ s/cust_pay\.payinfo/cust_pay.paymask/gi; + + push @search, + "( $search OR ( cust_pay.paymask IS NOT NULL AND $masksearch ) )"; + + } + } + + if ( $cgi->param('payinfo') ) { + $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ + or die "illegal payinfo ". $cgi->param('payinfo'); + push @search, "cust_pay.payinfo = '$1'"; + } + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + push @search, "_date >= $beginning ", + "_date <= $ending"; + + push @search, FS::UI::Web::parse_lt_gt($cgi, 'paid' ); + + $orderby = '_date'; + + } elsif ( $cgi->param('magic') eq 'paybatch' ) { + + $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/ + or die "illegal paybatch: ". $cgi->param('paybatch'); + + push @search, "paybatch = '$1'"; + + $orderby = "LOWER(company || ' ' || last || ' ' || first )"; + + } else { + die "unknown search magic: ". $cgi->param('magic'); + } + + #here is the agent virtualization + push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; + + my $search = ' WHERE '. join(' AND ', @search); + + $count_query = "SELECT COUNT(*), SUM(paid) ". + "FROM cust_pay LEFT JOIN cust_main USING ( custnum )". + $search; + + $sql_query = { + 'table' => 'cust_pay', + 'select' => join(', ', + 'cust_pay.*', + 'cust_main.custnum as cust_main_custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'hashref' => {}, + 'extra_sql' => "$search ORDER BY $orderby", + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + }; + +} else { + + #hmm... is this still used? + + $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo"; + my $payinfo = $1; + + $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby"; + my $payby = $1; + + $count_query = "SELECT COUNT(*), SUM(paid) FROM cust_pay". + " WHERE payinfo = '$payinfo' AND payby = '$payby'". + " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql; + + $sql_query = { + 'table' => 'cust_pay', + 'hashref' => { 'payinfo' => $payinfo, + 'payby' => $payby }, + 'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql. + " ORDER BY _date", + }; + +} + +my $link = [ "${p}view/cust_pay.html?paynum=", 'paynum' ]; + +my $cust_link = sub { + my $cust_pay = shift; + $cust_pay->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +</%init> diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi new file mode 100755 index 000000000..60dca8cba --- /dev/null +++ b/httemplate/search/cust_pay_batch.cgi @@ -0,0 +1,191 @@ +<% include('elements/search.html', + 'title' => 'Batch payment details', + 'name' => 'batch details', + 'query' => $sql_query, + 'count_query' => $count_query, + 'html_init' => $pay_batch ? $html_init : '', + 'header' => [ '#', + 'Inv #', + 'Customer', + 'Customer', + 'Card Name', + 'Card', + 'Exp', + 'Amount', + 'Status', + ], + 'fields' => [ sub { + shift->[0]; + }, + sub { + shift->[1]; + }, + sub { + shift->[2]; + }, + sub { + my $cpb = shift; + $cpb->[3] . ', ' . $cpb->[4]; + }, + sub { + shift->[5]; + }, + sub { + my $cardnum = shift->[6]; + 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); + }, + sub { + shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my( $mon, $year ) = ( $2, $1 ); + $mon = "0$mon" if length($mon) == 1; + "$mon/$year"; + }, + sub { + shift->[8]; + }, + sub { + shift->[9]; + }, + ], + 'align' => 'lllllllrl', + 'links' => [ ['', sub{'#';}], + ["${p}view/cust_bill.cgi?", sub{shift->[1];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ["${p}view/cust_main.cgi?", sub{shift->[2];},], + ], + ) +%> +<%init> + +my $conf = new FS::Conf; + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports') + || $FS::CurrentUser::CurrentUser->access_right('Process batches') + || ( $cgi->param('custnum') + && $conf->exists('batch-enable') + #&& $FS::CurrentUser::CurrentUser->access_right('View customer batched payments') + ); + +my( $count_query, $sql_query ); +my $hashref = {}; +my @search = (); +my $orderby = 'paybatchnum'; + +my( $pay_batch, $batchnum ) = ( '', ''); +if ( $cgi->param('batchnum') && $cgi->param('batchnum') =~ /^(\d+)$/ ) { + push @search, "batchnum = $1"; + $pay_batch = qsearchs('pay_batch', { 'batchnum' => $1 } ); + die "Batch $1 not found!" unless $pay_batch; + $batchnum = $pay_batch->batchnum; +} + +if ( $cgi->param('custnum') && $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @search, "custnum = $1"; +} + +if ( $cgi->param('status') && $cgi->param('status') =~ /^(\w)$/ ) { + push @search, "pay_batch.status = '$1'"; +} + +if ( $cgi->param('payby') ) { + $cgi->param('payby') =~ /^(CARD|CHEK)$/ + or die "illegal payby " . $cgi->param('payby'); + + push @search, "cust_pay_batch.payby = '$1'"; +} + +if ( not $cgi->param('dcln') ) { + push @search, "cpb.status IS DISTINCT FROM 'Approved'"; +} + +my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +unless ($pay_batch){ + push @search, "pay_batch.upload >= $beginning" if ($beginning); + push @search, "pay_batch.upload <= $ending" if ($ending < 4294967295);#2^32-1 + $orderby = "pay_batch.download,paybatchnum"; +} + +push @search, $FS::CurrentUser::CurrentUser->agentnums_sql; +my $search = ' WHERE ' . join(' AND ', @search); + +$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' . + 'LEFT JOIN cust_main USING ( custnum ) ' . + 'LEFT JOIN pay_batch USING ( batchnum )' . + $search; + +#grr +$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," . + "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " . + "FROM cust_pay_batch AS cpb " . + 'LEFT JOIN cust_main USING ( custnum ) ' . + 'LEFT JOIN pay_batch USING ( batchnum ) ' . + "$search ORDER BY $orderby"; + +my $html_init = ''; +if ( $pay_batch ) { + my $fixed = $conf->config('batch-fixed_format-'. $pay_batch->payby); + if ( + $pay_batch->status eq 'O' + || ( $pay_batch->status eq 'I' + && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') + ) + ) { + $html_init .= qq!<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">!; + if ( $fixed ) { + $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; + } else { + $html_init .= qq!Download batch in format <SELECT NAME="format">!. + qq!<OPTION VALUE="">Default batch mode</OPTION>!. + qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>!. + qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!. + qq!<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>!. + qq!<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>!. + qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!. + qq!</SELECT>!; + } + $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM><BR>!; + } + + if ( + $pay_batch->status eq 'I' + || ( $pay_batch->status eq 'R' + && $FS::CurrentUser::CurrentUser->access_right('Reprocess batches') + ) + ) { + $html_init .= qq!<FORM ACTION="$p/misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">!. + qq!Upload results<BR>!. + qq!Filename <INPUT TYPE="file" NAME="batch_results"><BR>!; + if ( $fixed ) { + $html_init .= qq!<INPUT TYPE="hidden" NAME="format" VALUE="$fixed">!; + } else { + $html_init .= qq!Format <SELECT NAME="format">!. + qq!<OPTION VALUE="">Default batch mode</OPTION>!. + qq!<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>!. + qq!<OPTION VALUE="csv-chase_canada-E-xactBatch">CSV file for Chase Canada E-xactBatch</OPTION>!. + qq!<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>!. + qq!<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>!. + qq!<OPTION VALUE="ach-spiritone">Spiritone ACH batch</OPTION>!. + qq!</SELECT><BR>!; + } + $html_init .= qq!<INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum">!; + $html_init .= '<INPUT TYPE="submit" VALUE="Upload"></FORM><BR>'; + } + +} + +if ($pay_batch) { + my $sth = dbh->prepare($count_query) or die dbh->errstr. "doing $count_query"; + $sth->execute or die "Error executing \"$count_query\": ". $sth->errstr; + my $cards = $sth->fetchrow_arrayref->[0]; + + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". $batchnum; + $sth = dbh->prepare($st) or die dbh->errstr. "doing $st"; + $sth->execute or die "Error executing \"$st\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + + $html_init .= "$cards credit card payments batched<BR>\$" . + sprintf("%.2f", $total) ." total in batch<BR>"; +} + +</%init> diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi new file mode 100755 index 000000000..20f1154c0 --- /dev/null +++ b/httemplate/search/cust_pkg.cgi @@ -0,0 +1,237 @@ +<% include( 'elements/search.html', + 'html_init' => $html_init, + 'title' => 'Package Search Results', + 'name' => 'packages', + 'query' => $sql_query, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ '#', + 'Package', + 'Class', + 'Status', + 'Freq.', + 'Setup', + 'Last bill', + 'Next bill', + 'Adjourn', + 'Susp.', + 'Expire', + 'Cancel', + 'Reason', + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), + 'Services', + ], + 'fields' => [ + 'pkgnum', + sub { #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->pkg; # ' - '. $part_pkg->comment; + $_[0]->pkg; # ' - '. $_[0]->comment; + }, + 'classname', + sub { ucfirst(shift->status); }, + sub { #shift->part_pkg->freq_pretty; + + #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->freq_pretty; + + FS::part_pkg::freq_pretty(shift); + }, + + #sub { time2str('%b %d %Y', shift->setup); }, + #sub { time2str('%b %d %Y', shift->last_bill); }, + #sub { time2str('%b %d %Y', shift->bill); }, + #sub { time2str('%b %d %Y', shift->susp); }, + #sub { time2str('%b %d %Y', shift->expire); }, + #sub { time2str('%b %d %Y', shift->get('cancel')); }, + ( map { time_or_blank($_) } + qw( setup last_bill bill adjourn susp expire cancel ) ), + + sub { my $self = shift; + my $return = ''; + if ($self->getfield('cancel') || + $self->getfield('suspend')) { + my $reason = $self->last_reason;# too inefficient? + $return = $reason->reason if $reason; + + } + $return; + }, + + \&FS::UI::Web::cust_fields, + #sub { '<table border=0 cellspacing=0 cellpadding=0 STYLE="border:none">'. + # join('', map { '<tr><td align="right" style="border:none">'. $_->[0]. + # ':</td><td style="border:none">'. $_->[1]. '</td></tr>' } + # shift->labels + # ). + # '</table>'; + # }, + sub { + [ map { + [ + { 'data' => $_->[0]. ':', + 'align'=> 'right', + }, + { 'data' => $_->[1], + 'align'=> 'left', + 'link' => $p. 'view/' . + $_->[2]. '.cgi?'. $_->[3], + }, + ]; + } shift->labels + ]; + }, + ], + 'color' => [ + '', + '', + '', + sub { shift->statuscolor; }, + '', + '', + '', + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + '', + ], + 'style' => [ '', '', '', 'b', '', '', '', '', '', '', '', '', '', + FS::UI::Web::cust_styles() ], + 'size' => [ '', '', '', '-1' ], + 'align' => 'rlcclrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r', + 'links' => [ + $link, + $link, + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), + '', + ], + 'extra_choices_callback'=> $extra_choices, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); + +# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); + + my %search_hash = (); + + $search_hash{'query'} = $cgi->keywords; + + for my $param (qw(agentnum magic status classnum pkgpart)) { + $search_hash{$param} = $cgi->param($param) + if $cgi->param($param); + } + +### +# parse dates +### + +#false laziness w/report_cust_pkg.html +my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, +); + +foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295 + or $disable{$cgi->param('status')}->{$field}; + + $search_hash{$field} = [ $beginning, $ending ]; + +} + +my $sql_query = FS::cust_pkg->search_sql(\%search_hash); +my $count_query = delete($sql_query->{'count_query'}); + +my $link = sub { + [ "${p}view/cust_main.cgi?".shift->custnum.'#cust_pkg', 'pkgnum' ]; +}; + +my $clink = sub { + my $cust_pkg = shift; + $cust_pkg->cust_main_custnum + ? [ "${p}view/cust_main.cgi?", 'custnum' ] + : ''; +}; + +#if ( scalar(@cust_pkg) == 1 ) { +# print $cgi->redirect("${p}view/cust_main.cgi?". $cust_pkg[0]->custnum. +# "#cust_pkg". $cust_pkg[0]->pkgnum ); + +# my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); +# my $rowspan = scalar(@cust_svc) || 1; + +# my $n2 = ''; +# foreach my $cust_svc ( @cust_svc ) { +# my($label, $value, $svcdb) = $cust_svc->label; +# my $svcnum = $cust_svc->svcnum; +# my $sview = $p. "view"; +# print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!, +# qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!; +# $n2="</TR><TR>"; +# } + +sub time_or_blank { + my $column = shift; + return sub { + my $record = shift; + my $value = $record->get($column); #mmm closures + $value ? time2str('%b %d %Y', $value ) : ''; + }; +} + +my $html_init = ''; +for (qw (overlibmws overlibmws_iframe overlibmws_draggable iframecontentmws)) +{ + $html_init .= + qq!<SCRIPT TYPE="text/javascript" SRC="$fsurl/elements/$_.js"></SCRIPT>!; +} + +my $extra_choices = sub { + my $query = shift; + my $choices = ''; + + my $url = qq!overlib( OLiframeContent('!. popurl(2). + qq!misc/bulk_change_pkg.cgi?$query', 768, 336, !. + qq!'bulk_pkg_change_popup' ), CAPTION, 'Change Packages'!. + qq!, STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, !. + qq!CLOSECLICK ); return false;!; + + if ($FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages')) { + $choices .= qq!<BR><A HREF="javascript:void(0);"!. + qq!onClick="$url">Change these packages</A>!; + } + + return $choices; +}; + +</%init> diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html new file mode 100644 index 000000000..3beca4d83 --- /dev/null +++ b/httemplate/search/cust_svc.html @@ -0,0 +1,138 @@ +<% include( 'elements/search.html', + 'title' => 'Service search results', + 'name' => 'services', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + # package? + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + sub { + #$_[0]->svc. ': '. $_[0]->label; + my($label, $value, $svcdb) = $_[0]->label; + "$label: $value"; + }, + # package? + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + # package? + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rl'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +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 @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'"; + + 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"); +} + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +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 $orderby", +}; + +my $count_query = "SELECT COUNT(*) FROM cust_svc $addl_from $extra_sql"; + +my $link = sub { + my $cust_svc = shift; + my $url = svc_url( + 'm' => $m, + 'action' => 'view', + #'part_svc' => $cust_svc->part_svc, + 'svcdb' => $cust_svc->svcdb, #we have it from the joined search + #'svc' => $cust_svc, #redundant + 'query' => '', + ); + [ $url, 'svcnum' ]; +}; + +my $link_cust = sub { + my $cust_svc = shift; + if ( $cust_svc->custnum ) { + [ "${p}view/cust_main.cgi?", 'custnum' ]; + } else { + ''; + } +}; + +</%init> diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi new file mode 100644 index 000000000..604502d6f --- /dev/null +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -0,0 +1,182 @@ +<% include( 'elements/search.html', + 'title' => 'Tax exemptions', + 'name' => 'tax exemptions', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Month', + 'Amount', + 'Line item', + 'Invoice', + 'Date', + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'exemptpkgnum', + sub { $_[0]->month. '/'. $_[0]->year; }, + sub { $money_char. $_[0]->amount; }, + + sub { + $_[0]->billpkgnum. ': '. + ( $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + ). + ' ('. + ( $_[0]->setup > 0 + ? $money_char. $_[0]->setup. ' setup' + : '' + ). + ( $_[0]->setup > 0 && $_[0]->recur > 0 + ? ' / ' + : '' + ). + ( $_[0]->recur > 0 + ? $money_char. $_[0]->recur. ' recur' + : '' + ). + ')'; + }, + + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, + + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + + '', + $ilink, + $ilink, + + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rrrlrc'.FS::UI::Web::cust_aligns(), # 'rlrrrc', + 'color' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%once> + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; + +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $join = " + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust + $join_pkg +"; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions'); + +my @where = (); + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +if ( $beginning || $ending ) { + push @where, "_date >= $beginning", + "_date <= $ending"; + #"payby != 'COMP'; +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + push @where, "agentnum = $1"; +} + +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + push @where, "cust_main.custnum = $1"; +} + +if ( $cgi->param('out') ) { + + push @where, " + 0 = ( + SELECT COUNT(*) FROM cust_main_county AS county_out + WHERE ( county_out.county = cust_main.county + OR ( county_out.county IS NULL AND cust_main.county = '' ) + OR ( county_out.county = '' AND cust_main.county IS NULL) + OR ( county_out.county IS NULL AND cust_main.county IS NULL) + ) + AND ( county_out.state = cust_main.state + OR ( county_out.state IS NULL AND cust_main.state = '' ) + OR ( county_out.state = '' AND cust_main.state IS NULL ) + OR ( county_out.state IS NULL AND cust_main.state IS NULL ) + ) + AND county_out.country = cust_main.country + AND county_out.tax > 0 + ) + "; + +} elsif ( $cgi->param('country' ) ) { + + my $county = dbh->quote( $cgi->param('county') ); + my $state = dbh->quote( $cgi->param('state') ); + my $country = dbh->quote( $cgi->param('country') ); + push @where, "( county = $county OR $county = '' )", + "( state = $state OR $state = '' )", + " country = $country"; + push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') ) + if $cgi->param('taxclass'); + +} + +my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; + +my $count_query = "SELECT COUNT(*), SUM(amount)". + " FROM cust_tax_exempt_pkg $join $where"; + +my $query = { + 'table' => 'cust_tax_exempt_pkg', + 'addl_from' => $join, + 'hashref' => {}, + 'select' => join(', ', + 'cust_tax_exempt_pkg.*', + 'cust_bill_pkg.*', + 'cust_bill.*', + 'part_pkg.pkg', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $where, +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +</%init> diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html new file mode 100644 index 000000000..e1bc02450 --- /dev/null +++ b/httemplate/search/elements/search.html @@ -0,0 +1,853 @@ +<%doc> + +Example: + + include( 'elements/search.html', + + ### + # required + ### + + 'title' => 'Page title', + + 'name_singular' => 'item', #singular name for the records returned + #OR# # (preferred, will be pluralized automatically) + 'name' => 'items', #plural name for the records returned + # (deprecated, will be singularlized + # simplisticly) + + #literal SQL query string (deprecated?) or qsearch hashref + 'query' => { + 'table' => 'tablename', + #everything else is optional... + 'hashref' => { 'field' => 'value', + 'field' => { 'op' => '<', + 'value' => '54', + }, + }, + 'select' => '*', + 'addl_from' => '', #'LEFT JOIN othertable USING ( key )', + 'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff', + 'order_by' => 'ORDER BY something', + + }, + # "select * from tablename"; + + #required unless 'query' is an SQL query string (shouldn't be...) + 'count_query' => 'SELECT COUNT(*) FROM tablename', + + ### + # recommended / common + ### + + #listref of column labels, <TH> + #recommended unless 'query' is an SQL query string + # (if not specified the database column names will be used) + 'header' => [ '#', + 'Item', + { 'label' => 'Another Item', + + }, + ], + + #listref - each item is a literal column name (or method) or coderef + #if not specified all columns will be shown + 'fields' => [ + 'column', + sub { my $row = shift; $row->column; }, + ], + + #redirect if there's only one item... + # listref of URL base and column name (or method) + # or a coderef that returns the same + 'redirect' => + + ### + # optional + ### + + # some HTML callbacks... + 'menubar' => '', #menubar arrayref + 'html_init' => '', #after the header/menubar and before the pager + 'html_form' => '', #after the pager, right before the results + # (only shown if there are results) + # (use this for any form-opening tag rather than + # html_init, to avoid a nested form) + 'html_foot' => '', #at the bottom + 'html_posttotal' => '', #at the bottom + # (these three can be strings or coderefs) + + 'count_addl' => [], #additional count fields listref of sprintf strings or coderefs + # [ $money_char.'%.2f total paid', ], + + #second (smaller) header line, currently only for HTML + 'header2 => [ '#', + 'Item', + { 'label' => 'Another Item', + + }, + ], + + #listref of column footers + 'footer' => [], + + #disabling things + 'disable_download' => '', # set true to hide the CSV/Excel download links + 'disable_nonefound' => '', # set true to disable the "No matching Xs found" + # message + + #handling "disabled" fields in the records + 'disableable' => 1, # set set to 1 (or column position for "disabled" + # status col) to enable if this table has a "disabled" + # field, to hide disabled records & have + # "show disabled/hide disabled" links + #(can't be used with a literal query) + 'disabled_statuspos' => 3, #optional position (starting from 0) to insert + #a Status column when showing disabled records + #(query needs to be a qsearch hashref and + # header & fields need to be defined) + + #handling agent virtualization + 'agent_virt' => 1, # set true if this search should be agent-virtualized + 'agent_null_right' => 'Access Right', #opt. right to view global records + 'agent_pos' => 3, #optional position (starting from 0) to insert + #an Agent column + #(query needs to be a qsearch hashref and + # header & fields need to be defined) + + # link & display properties for fields + + #listref - each item is the empty string, + # or a listref of link and method name to append, + # or a listref of link and coderef to run and append + # or a coderef that returns such a listref + 'links' => [],` + + #listref - each item is the empty string, + # or a string onClick handler for the corresponding link + # or a coderef that returns string onClick handler + 'link_onclicks' => [], + + #one letter for each column, left/right/center/none + # or pass a listref with full values: [ 'left', 'right', 'center', '' ] + 'align' => 'lrc.', + + #listrefs of ( scalars or coderefs ) + #currently only HTML, maybe eventually Excel too + 'color' => [], + 'size' => [], + 'style' => [], #<B> or <I>, etc. + 'cell_style' => [], #STYLE= attribute of TR, very HTML-specific... + + ); + +</%doc> +% if ( $type eq 'csv' ) { +% +% #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes +% http_header('Content-Type' => 'text/plain' ); +% +% my $csv = new Text::CSV_XS { 'always_quote' => 1, +% 'eol' => "\n", #"\015\012", #"\012" +% }; +% +% $csv->combine(@$header); #or die $csv->status; +% +<% $csv->string %> +% +% +% foreach my $row ( @$rows ) { +% +% if ( $opt{'fields'} ) { +% +% my @line = (); +% +% foreach my $field ( @{$opt{'fields'}} ) { +% if ( ref($field) eq 'CODE' ) { +% push @line, map { +% ref($_) eq 'ARRAY' +% ? '(N/A)' #unimplemented +% : $_; +% } +% &{$field}($row); +% } else { +% push @line, $row->$field(); +% } +% } +% +% $csv->combine(@line); #or die $csv->status; +% +% } else { +% $csv->combine(@$row); #or die $csv->status; +% } +% +% +<% $csv->string %> +% +% +% } +% +% #} elsif ( $type eq 'excel' ) { +% } elsif ( $type =~ /\.xls$/ ) { +% +% #http_header('Content-Type' => 'application/excel' ); #eww +% #http_header('Content-Type' => 'application/msexcel' ); #alas +% #http_header('Content-Type' => 'application/x-msexcel' ); #? +% +% #http://support.microsoft.com/kb/199841 +% http_header('Content-Type' => 'application/vnd.ms-excel' ); +% +% #http://support.microsoft.com/kb/812935 +% #http://support.microsoft.com/kb/323308 +% $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 $worksheet = $workbook->add_worksheet(substr($opt{'title'},0,31)); +% +% my($r,$c) = (0,0); +% +% $worksheet->write($r, $c++, $_) foreach @$header; +% +% foreach my $row ( @$rows ) { +% $r++; +% $c = 0; +% +% if ( $opt{'fields'} ) { +% +% #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% +% foreach my $field ( @{$opt{'fields'}} ) { +% #my $align = $aligns ? shift @$aligns : ''; +% #$align = " ALIGN=$align" if $align; +% #my $a = ''; +% #if ( $links ) { +% # my $link = shift @$links; +% # $link = &{$link}($row) if ref($link) eq 'CODE'; +% # if ( $link ) { +% # my( $url, $method ) = @{$link}; +% # if ( ref($method) eq 'CODE' ) { +% # $a = $url. &{$method}($row); +% # } else { +% # $a = $url. $row->$method(); +% # } +% # $a = qq(<A HREF="$a">); +% # } +% #} +% if ( ref($field) eq 'CODE' ) { +% foreach my $value ( &{$field}($row) ) { +% if ( ref($value) eq 'ARRAY' ) { +% $worksheet->write($r, $c++, '(N/A)' ); #unimplemented +% } else { +% $worksheet->write($r, $c++, $value ); +% } +% } +% } else { +% $worksheet->write($r, $c++, $row->$field() ); +% } +% } +% +% } else { +% $worksheet->write($r, $c++, $_) foreach @$row; +% } +% +% } +% +% $workbook->close();# or die "Error creating .xls file: $!"; +% +% http_header('Content-Length' => length($data) ); +% +<% $data %> +% +% +% } else { # regular HTML +% +% if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1 +% && $type ne 'html-print' +% ) { +% my $redirect = $opt{'redirect'}; +% $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE'; +% my( $url, $method ) = @$redirect; +% redirect( $url. $rows->[0]->$method() ); +% } else { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = PL($opt{'name_singular'}); +% } +% ( my $xlsname = $opt{'name'} ) =~ s/\W//g; +% if ( $total == 1 ) { +% if ( $opt{'name_singular'} ) { +% $opt{'name'} = $opt{'name_singular'} +% } else { +% #$opt{'name'} =~ s/s$// if $total == 1; +% $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1; +% } +% } +% +% if ( $type eq 'html-print' ) { + + <% include( '/elements/header-popup.html', $opt{'title'} ) %> + +% } else { +% +% my @menubar = (); +% if ( $opt{'menubar'} ) { +% @menubar = @{ $opt{'menubar'} }; +% #} else { +% # @menubar = ( 'Main menu' => $p ); +% } + + <% include( '/elements/header.html', $opt{'title'}, + include( '/elements/menubar.html', @menubar ) + ) + %> + + <% defined($opt{'html_init'}) + ? ( ref($opt{'html_init'}) + ? &{$opt{'html_init'}}() + : $opt{'html_init'} + ) + : '' + %> + +% } + +% unless ( $total ) { +% unless ( $opt{'disable_nonefound'} ) { + No matching <% $opt{'name'} %> found.<BR> +% } +% } +% +% if ( $total || $opt{'disableable'} ) { #hmm... and there *are* ones to show?? + + <TABLE> + <TR> + + <TD VALIGN="bottom"> + + <FORM> + + <% $total %> total <% $opt{'name'} %> + +% if ( $confmax && $total > $confmax && $type ne 'html-print' ) { +% $cgi->delete('maxrecords'); +% $cgi->param('_dummy', 1); + +%# ( show <SELECT NAME="maxrecords" onChange="this.form.submit();"> + ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->self_url %>;maxrecords=' + this.options[this.selectedIndex].value;"> + +% foreach my $max ( map { $_ * $confmax } qw( 1 5 10 25 ) ) { + <OPTION VALUE="<% $max %>" <% ( $maxrecords == $max ) ? 'SELECTED' : '' %>><% $max %></OPTION> +% } + + </SELECT> per page ) + +% $cgi->param('maxrecords', $maxrecords); +% } + +% if ( defined($opt{'html_posttotal'}) && $type ne 'html-print' ) { + <% ref($opt{'html_posttotal'}) + ? &{$opt{'html_posttotal'}}() + : $opt{'html_posttotal'} + %> +% } + <BR> + +% if ( $opt{'count_addl'} ) { +% my $n=0; +% foreach my $count ( @{$opt{'count_addl'}} ) { +% my $data = $count_arrayref->[++$n]; +% if ( ref($count) ) { + <% &{ $count }( $data ) %> +% } else { + <% sprintf( $count, $data ) %><BR> +% } +% } +% } + </FORM> + + </TD> + +% unless ( $opt{'disable_download'} || $type eq 'html-print' ) { + + <TD ALIGN="right"> + + Download full results<BR> + +% $cgi->param('_type', "$xlsname.xls" ); + as <A HREF="<% $cgi->self_url %>">Excel spreadsheet</A><BR> + +% $cgi->param('_type', 'csv'); + as <A HREF="<% $cgi->self_url %>">CSV file</A><BR> + +% $cgi->param('_type', 'html-print'); + as <A HREF="<% $cgi->self_url %>">printable copy</A> + + <% $opt{'extra_choices_callback'} + ? &{$opt{'extra_choices_callback'}}($cgi->query_string) + : '' + %> + + </TD> +% $cgi->param('_type', "html" ); +% } + + </TR> + <TR> + <TD COLSPAN=2> + +% my $pager = ''; +% unless ( $type eq 'html_print' ) { + + <% $pager = include( '/elements/pager.html', + 'offset' => $offset, + 'num_rows' => scalar(@$rows), + 'total' => $total, + 'maxrecords' => $maxrecords, + ) + %> + + <% defined($opt{'html_form'}) + ? ( ref($opt{'html_form'}) + ? &{$opt{'html_form'}}() + : $opt{'html_form'} + ) + : '' + %> + +% } + + <% include('/elements/table-grid.html') %> + + <TR> +% my $h2 = 0; +% foreach my $header ( @{ $opt{header} } ) { +% my $label = ref($header) ? $header->{label} : $header; +% my $rowspan = 1; +% my $style = ''; +% if ( $opt{header2} ) { +% if ( !length($opt{header2}->[$h2]) ) { +% $rowspan = 2; +% splice @{ $opt{header2} }, $h2, 1; +% } else { +% $h2++; +% $style = 'STYLE="border-bottom: none"' +% } +% } + <TH CLASS = "grid" + BGCOLOR = "#cccccc" + ROWSPAN = "<% $rowspan %>" + <% $style %> + + > + <% $label %> + </TH> +% } + </TR> + +% if ( $opt{header2} ) { + <TR> +% foreach my $header ( @{ $opt{header2} } ) { +% my $label = ref($header) ? $header->{label} : $header; + <TH CLASS="grid" BGCOLOR="#cccccc"> + <FONT SIZE="-1"><% $label %></FONT> + </TH> +% } + </TR> +% } + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% foreach my $row ( @$rows ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> + +% if ( $opt{'fields'} ) { +% +% my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : ''; +% my $onclicks = $opt{'link_onclicks'} ? [ @{$opt{'link_onclicks'}} ] : []; +% my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : ''; +% my $colors = $opt{'color'} ? [ @{$opt{'color'}} ] : []; +% my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : []; +% my $styles = $opt{'style'} ? [ @{$opt{'style'}} ] : []; +% my $cstyles = $opt{'cell_style'} ? [ @{$opt{'cell_style'}} ] : []; +% +% foreach my $field ( +% +% map { +% if ( ref($_) eq 'ARRAY' ) { +% +% my $tableref = $_; +% +% '<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 WIDTH="100%">'. +% +% join('', map { +% +% my $rowref = $_; +% +% '<tr>'. +% +% join('', map { +% +% my $e = $_; +% +% '<TD '. +% join(' ', map { +% uc($_).'="'. $e->{$_}. '"'; +% } +% grep exists($e->{$_}), +% qw( align bgcolor colspan rowspan +% style valign width ) +% ). +% '>'. +% +% ( $e->{'link'} +% ? '<A HREF="'. $e->{'link'}. '">' +% : '' +% ). +% ( $e->{'size'} +% ? '<FONT SIZE="'.uc($e->{'size'}).'">' +% : '' +% ). +% ( $e->{'data_style'} +% ? '<'. uc($e->{'data_style'}). '>' +% : '' +% ). +% $e->{'data'}. +% ( $e->{'data_style'} +% ? '</'. uc($e->{'data_style'}). '>' +% : '' +% ). +% ( $e->{'size'} ? '</FONT>' : '' ). +% ( $e->{'link'} ? '</A>' : '' ). +% '</td>'; +% +% } @$rowref ). +% +% '</tr>'; +% } @$tableref ). +% +% '</table>'; +% +% } else { +% $_; +% } +% } +% +% map { +% if ( ref($_) eq 'CODE' ) { +% &{$_}($row); +% } else { +% $row->$_(); +% } +% } +% @{$opt{'fields'}} +% +% ) { +% +% my $class = ( $field =~ /^<TABLE/i ) ? 'inv' : 'grid'; +% +% my $align = $aligns ? shift @$aligns : ''; +% $align = " ALIGN=$align" if $align; +% +% my $a = ''; +% if ( $links ) { +% my $link = shift @$links; +% $link = &{$link}($row) +% if ref($link) eq 'CODE'; +% +% my $onclick = shift @$onclicks; +% $onclick = &{$onclick}($row) +% if ref($onclick) eq 'CODE'; +% $onclick = qq( onClick="$onclick") if $onclick; +% +% if ( $link ) { +% my( $url, $method ) = @{$link}; +% if ( ref($method) eq 'CODE' ) { +% $a = $url. &{$method}($row); +% } else { +% $a = $url. $row->$method(); +% } +% $a = qq(<A HREF="$a"$onclick>); +% } +% } +% +% my $font = ''; +% my $color = shift @$colors; +% $color = &{$color}($row) if ref($color) eq 'CODE'; +% my $size = shift @$sizes; +% $size = &{$size}($row) if ref($size) eq 'CODE'; +% if ( $color || $size ) { +% $font = '<FONT '. +% ( $color ? "COLOR=#$color " : '' ). +% ( $size ? qq(SIZE="$size" ) : '' ). +% '>'; +% } +% +% my($s, $es) = ( '', '' ); +% my $style = shift @$styles; +% $style = &{$style}($row) if ref($style) eq 'CODE'; +% if ( $style ) { +% $s = join( '', map "<$_>", split('', $style) ); +% $es = join( '', map "</$_>", split('', $style) ); +% } +% +% my $cstyle = shift @$cstyles; +% $cstyle = &{$cstyle}($row) if ref($cstyle) eq 'CODE'; +% $cstyle = qq(STYLE="$cstyle") +% if $cstyle; + + <TD CLASS="<% $class %>" BGCOLOR="<% $bgcolor %>" <% $align %> <% $cstyle %>><% $font %><% $a %><% $s %><% $field %><% $es %><% $a ? '</A>' : '' %><% $font ? '</FONT>' : '' %></TD> + +% } +% +% } else { +% +% foreach ( @$row ) { + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $_ %></TD> +% } +% +% } + + </TR> + +% } + +% if ( $opt{'footer'} ) { + + <TR> + +% foreach my $footer ( @{ $opt{'footer'} } ) { + <TD CLASS="grid" BGCOLOR="#dddddd" STYLE="border-top: dashed 1px black;"><i><% $footer %></i></TD> +% } + + </TR> +% } + + </TABLE> + + <% $pager %> + + </TD> + </TR> + </TABLE> +% } + +% if ( $type eq 'html-print' ) { + + </BODY></HTML> + +% } else { + + <% defined($opt{'html_foot'}) + ? ( ref($opt{'html_foot'}) + ? &{$opt{'html_foot'}}() + : $opt{'html_foot'} + ) + : '' + %> + + <% include( '/elements/footer.html' ) %> + +% } + +% } +% +% } +<%init> + +my(%opt) = @_; +#warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n"; + +my $curuser = $FS::CurrentUser::CurrentUser; + +my %align = ( + 'l' => 'left', + 'r' => 'right', + 'c' => 'center', + ' ' => '', + '.' => '', +); +$opt{align} = [ map $align{$_}, split(//, $opt{align}) ], + unless !$opt{align} || ref($opt{align}); + +if ( $opt{'agent_virt'} ) { + + my $agentnums_sql = $curuser->agentnums_sql( + 'null_right' => $opt{'agent_null_right'} + ); + + $opt{'query'}{'extra_sql'} .= + ( $opt{'query'} =~ /WHERE/i ? ' AND ' : ' WHERE ' ). + $agentnums_sql; + $opt{'count_query'} .= + ( $opt{'count_query'} =~ /WHERE/i ? ' AND ' : ' WHERE ' ). + $agentnums_sql; + + if ( $opt{'agent_pos'} || $opt{'agent_pos'} eq '0' + and scalar($curuser->agentnums) > 1 ) { + #false laziness w/statuspos above + my $pos = $opt{'agent_pos'}; + + foreach my $att (qw( align style color size )) { + $opt{$att} ||= [ map '', @{ $opt{'fields'} } ]; + } + + splice @{ $opt{'header'} }, $pos, 0, 'Agent'; + splice @{ $opt{'align'} }, $pos, 0, 'c'; + splice @{ $opt{'style'} }, $pos, 0, ''; + splice @{ $opt{'size'} }, $pos, 0, ''; + splice @{ $opt{'fields'} }, $pos, 0, + sub { $_[0]->agentnum ? $_[0]->agent->agent : '(global)'; }; + splice @{ $opt{'color'} }, $pos, 0, ''; + splice @{ $opt{'links'} }, $pos, 0, '' #[ 'agent link?', 'agentnum' ] + if $opt{'links'}; + splice @{ $opt{'link_onclicks'} }, $pos, 0, '' + if $opt{'link_onclicks'}; + + } + +} + +if ( $opt{'disableable'} ) { + + unless ( $cgi->param('showdisabled') ) { #modify searches + + $opt{'query'}{'hashref'}{'disabled'} = ''; + $opt{'query'}{'extra_sql'} =~ s/^\s*WHERE/ AND/i; + + $opt{'count_query'} .= + ( $opt{'count_query'} =~ /WHERE/i ? ' AND ' : ' WHERE ' ). + "( disabled = '' OR disabled IS NULL )"; + + } elsif ( $opt{'disabled_statuspos'} + || $opt{'disabled_statuspos'} eq '0' ) { #add status column + + my $pos = $opt{'disabled_statuspos'}; + + foreach my $att (qw( align style color size )) { + $opt{$att} ||= [ map '', @{ $opt{'fields'} } ]; + } + + splice @{ $opt{'header'} }, $pos, 0, 'Status'; + splice @{ $opt{'align'} }, $pos, 0, 'c'; + splice @{ $opt{'style'} }, $pos, 0, 'b'; + splice @{ $opt{'size'} }, $pos, 0, ''; + splice @{ $opt{'fields'} }, $pos, 0, + sub { shift->disabled ? 'DISABLED' : 'Active'; }; + splice @{ $opt{'color'} }, $pos, 0, + sub { shift->disabled ? 'FF0000' : '00CC00'; }; + splice @{ $opt{'links'} }, $pos, 0, '' + if $opt{'links'}; + splice @{ $opt{'link_onlicks'} }, $pos, 0, '' + if $opt{'link_onlicks'}; + } + + #add show/hide disabled links + my $items = $opt{'name'} || PL($opt{'name_singular'}); + if ( $cgi->param('showdisabled') ) { + $cgi->param('showdisabled', 0); + $opt{'html_posttotal'} .= + '( <a href="'. $cgi->self_url. qq!">hide disabled $items</a> )!; + $cgi->param('showdisabled', 1); + } else { + $cgi->param('showdisabled', 1); + $opt{'html_posttotal'} .= + '( <a href="'. $cgi->self_url. qq!">show disabled $items</a> )!; + $cgi->param('showdisabled', 0); + } + +} + +my $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|html(-print)?)$/ + ? $1 : 'html'; + +my $limit = ''; +my($confmax, $maxrecords, $total, $offset, $count_arrayref); + +unless ( $type =~ /^(csv|\w*\.xls)$/ ) { + + unless (exists($opt{count_query}) && length($opt{count_query})) { + ( $opt{count_query} = $opt{query} ) =~ + s/^\s*SELECT\s*(.*?)\s+FROM\s/SELECT COUNT(*) FROM /i; #silly vim:/ + } + + if ( $opt{disableable} && ! $cgi->param('showdisabled') ) { + $opt{count_query} .= + ( ( $opt{count_query} =~ /WHERE/i ) ? ' AND ' : ' WHERE ' ). + "( disabled = '' OR disabled IS NULL )"; + } + + unless ( $type eq 'html-print' ) { + + #setup some pagination things if we're in html mode + + my $conf = new FS::Conf; + $confmax = $conf->config('maxsearchrecordsperpage'); + if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) { + $maxrecords = $1; + } else { + $maxrecords ||= $confmax; + } + + $limit = $maxrecords ? "LIMIT $maxrecords" : ''; + + $offset = $cgi->param('offset') =~ /^(\d+)$/ ? $1 : 0; + $limit .= " OFFSET $offset" if $offset; + + } + + my $count_sth = dbh->prepare($opt{'count_query'}) + or die "Error preparing $opt{'count_query'}: ". dbh->errstr; + $count_sth->execute + or die "Error executing $opt{'count_query'}: ". $count_sth->errstr; + $count_arrayref = $count_sth->fetchrow_arrayref; + $total = $count_arrayref->[0]; + +} + +# run the query + +my $header = [ map { ref($_) ? $_->{'label'} : $_ } @{$opt{header}} ]; +my $rows; +if ( ref($opt{query}) ) { + + if ( $opt{disableable} && ! $cgi->param('showdisabled') ) { + #%search = ( 'disabled' => '' ); + $opt{'query'}->{'hashref'}->{'disabled'} = ''; + $opt{'query'}->{'extra_sql'} =~ s/^\s*WHERE/ AND/i; + } + + #eval "use FS::$opt{'query'};"; + $rows = [ qsearch({ + 'select' => $opt{'query'}->{'select'}, + 'table' => $opt{'query'}->{'table'}, + 'addl_from' => (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : ''), + 'hashref' => $opt{'query'}->{'hashref'} || {}, + 'extra_sql' => $opt{'query'}->{'extra_sql'}, + 'order_by' => $opt{'query'}->{'order_by'}. " $limit", + }) ]; +} else { + my $sth = dbh->prepare("$opt{'query'} $limit") + or die "Error preparing $opt{'query'}: ". dbh->errstr; + $sth->execute + or die "Error executing $opt{'query'}: ". $sth->errstr; + + #can get # of rows without fetching them all? + $rows = $sth->fetchall_arrayref; + + $header ||= $sth->{NAME}; +} + +</%init> diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html new file mode 100644 index 000000000..cd37e267b --- /dev/null +++ b/httemplate/search/inventory_item.html @@ -0,0 +1,125 @@ +<% include( 'elements/search.html', + 'title' => $title, + + #less lame to use Lingua:: something to pluralize + 'name' => $inventory_class->classname. 's', + + 'query' => { + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum }, + 'select' => join(', ', + 'inventory_item.*', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $extra_sql, + 'addl_from' => $addl_from, + }, + + 'count_query' => $count_query, + + 'header' => [ + '#', + $inventory_class->classname, + 'Service', + FS::UI::Web::cust_header(), + ], + + 'fields' => [ + 'itemnum', + 'item', + #'svcnum', #XXX proper full service customer link ala svc_acct + # "unallocated" ? "available" ? + sub { + #this could be way more efficient with a mixin + # like cust_main_Mixin that let us all all the methods + # on data we already have... + my $inventory_item = shift; + my $cust_svc = $inventory_item->cust_svc; + if ( $cust_svc ) { + my($label, $value) = $cust_svc->label; + "$label: $value"; + } else { + '(available)'; + } + }, + + \&FS::UI::Web::cust_fields, + + ], + 'align' => 'rll'.FS::UI::Web::cust_aligns(), + 'links' => [ + '', + '', + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $classnum = $cgi->param('classnum'); +$classnum =~ /^(\d+)$/ or errorpage("illegal classnum $classnum"); +$classnum = $1; + +my $inventory_class = qsearchs( { + 'table' => 'inventory_class', + 'hashref' => { 'classnum' => $classnum }, +} ); + +my $title = $inventory_class->classname. ' Inventory'; + +#little false laziness with SQL fragments in inventory_class.pm +my $extra_sql = ''; +if ( $cgi->param('avail') ) { + $extra_sql = 'AND ( svcnum IS NULL OR svcnum = 0 )'; + $title .= ' - Available'; +} elsif ( $cgi->param('used') ) { + $extra_sql = 'AND svcnum IS NOT NULL AND svcnum > 0'; + $title .= ' - In use'; +} + +my $count_query = + "SELECT COUNT(*) FROM inventory_item WHERE classnum = $classnum $extra_sql"; + +my $link = sub { + my $inventory_item = shift; + if ( $inventory_item->svcnum ) { + [ "${p}view/svc_acct.cgi?", 'svcnum' ]; + } else { + ''; + } +}; +my $link_cust = sub { + my $inventory_item = shift; + if ( $inventory_item->custnum ) { + [ "${p}view/cust_main.cgi?", 'custnum' ]; + } else { + ''; + } +}; + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +</%init> diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi new file mode 100755 index 000000000..cb2171799 --- /dev/null +++ b/httemplate/search/pay_batch.cgi @@ -0,0 +1,130 @@ +<% include( 'elements/search.html', + 'title' => 'Payment Batches', + 'name_singular' => 'batch', + 'query' => { 'table' => 'pay_batch', + 'hashref' => $hashref, + 'extra_sql' => "$extra_sql ORDER BY batchnum DESC", + }, + 'count_query' => "$count_query $extra_sql", + 'header' => [ 'Batch', + 'Type', + 'First Download', + 'Last Upload', + 'Item Count', + 'Amount', + 'Status', + ], + 'align' => 'rcllrrc', + 'fields' => [ 'batchnum', + sub { + FS::payby->shortname(shift->payby); + }, + sub { + my $self = shift; + my $_date = $self->download; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'O' ) { + 'Download batch'; + } else { + ''; + } + }, + sub { + my $self = shift; + my $_date = $self->upload; + if ( $_date ) { + time2str("%a %b %e %T %Y", $_date); + } elsif ( $self->status eq 'I' ) { + 'Upload results'; + } else { + ''; + } + }, + sub { + my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum; + my $sth = dbh->prepare($st) + or die dbh->errstr. "doing $st"; + $sth->execute + or die "Error executing \"$st\": ". $sth->errstr; + $sth->fetchrow_arrayref->[0]; + }, + sub { + $statusmap{shift->status}; + }, + ], + 'links' => [ + $link, + '', + sub { shift->status eq 'O' ? $link : '' }, + sub { shift->status eq 'I' ? $link : '' }, + ], + 'size' => [ + '', + '', + sub { shift->status eq 'O' ? "+1" : '' }, + sub { shift->status eq 'I' ? "+1" : '' }, + ], + 'style' => [ + '', + '', + sub { shift->status eq 'O' ? "b" : '' }, + sub { shift->status eq 'I' ? "b" : '' }, + ], + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports') + || $FS::CurrentUser::CurrentUser->access_right('Process batches'); + +my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved'); +my $hashref = {}; +my $count_query = 'SELECT COUNT(*) FROM pay_batch'; + +my($begin, $end) = ( '', '' ); + +my @where; +if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $begin = str2time($1); + push @where, "download >= $begin"; +} +if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { + $end = str2time($1) + 86399; + push @where, "download < $end"; +} + +my @status; +if ( $cgi->param('open') ) { + push @status, "O"; +} + +if ( $cgi->param('intransit') ) { + push @status, "I"; +} + +if ( $cgi->param('resolved') ) { + push @status, "R"; +} + +push @where, + scalar(@status) ? q!(status='! . join(q!' OR status='!, @status) . q!')! + : q!status='X'!; # kludgy, X is unused at present + +my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; + +my $link = [ "${p}search/cust_pay_batch.cgi?batchnum=", 'batchnum' ]; + +</%init> diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html new file mode 100644 index 000000000..5907169d8 --- /dev/null +++ b/httemplate/search/pay_batch.html @@ -0,0 +1,33 @@ +<% include('/elements/header.html', 'Batch criteria' ) %> + +<FORM ACTION="pay_batch.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show open batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="intransit" VALUE="1" CHECKED></TD> + <TD>Show in-transit batches</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="resolved" VALUE="1" CHECKED></TD> + <TD>Show resolved batches</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Batches"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html new file mode 100644 index 000000000..96391fcbd --- /dev/null +++ b/httemplate/search/prepay_credit.html @@ -0,0 +1,67 @@ +<% include( 'elements/search.html', + 'title' => 'Unused Prepaid Cards'. + ($agent ? ' for '. $agent->agent : ''), + 'menubar' => [ + 'Generate cards' => $p.'edit/prepay_credit.cgi', + ], + 'name' => 'prepaid cards', + 'query' => { 'table' => 'prepay_credit', + 'hashref' => $hashref, + }, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ '#', qw(Amount Time Upload Download Total Agent) ], + 'fields' => [ + 'identifier', + sub { sprintf('$%.2f', shift->amount ) }, + sub { my $c = shift; + $c->seconds ? duration_exact($c->seconds) : '' + }, + sub { my $c = shift; + $c->upbytes + ? FS::UI::bytecount::bytecount_unexact($c->upbytes) + : '' + }, + sub { my $c = shift; + $c->downbytes + ? FS::UI::bytecount::bytecount_unexact($c->downbytes) + : '' + }, + sub { my $c = shift; + $c->totalbytes + ? FS::UI::bytecount::bytecount_unexact($c->totalbytes) + : '' + }, + sub { my $agent = shift->agent; + $agent ? $agent->agent : ''; + }, + ], + 'links' => [ + '', + '', + '', + '', + '', + '', + sub { my $agent = shift->agent; + $agent ? [ "${p}view/agent.cgi?", 'agentnum' ] : ''; + }, + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $agent = ''; +my $hashref = {}; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { +$hashref->{agentnum} = $1; +$agent = qsearchs('agent', { 'agentnum' => $1 } ); +} + +my $count_query = 'SELECT COUNT(*) FROM prepay_credit'; +$count_query .= ' WHERE agentnum = '. $agent->agentnum if $agent; + +</%init> diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html new file mode 100644 index 000000000..125a6f7f6 --- /dev/null +++ b/httemplate/search/queue.html @@ -0,0 +1,138 @@ +<% include( 'elements/search.html', + 'title' => 'Job Queue', + 'name' => 'jobs', + 'html_form' => qq!<FORM NAME="jobForm" ACTION="$p/misc/queue.cgi" METHOD="POST">!, + 'query' => { 'table' => 'queue', + 'hashref' => $hashref, + 'extra_sql' => 'ORDER BY jobnum', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Job', + 'Args', + 'Date', + 'Status', + 'Account', # unless $hashref->{'svcnum'} + '', # checkbox column + ], + 'fields' => [ + 'jobnum', + 'job', + sub { + my $queue = shift; + if ( $dangerous + || $queue->job !~ /^FS::part_export::/ + || !$noactions + ) + { + encode_entities( join(' ', $queue->args) ); + } else { + ''; + } + }, + sub { + time2str( "%a %b %e %T %Y", shift->_date ); + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + $status .= ': '. $queue->statustext + if $queue->statustext; + my @queue_depend = $queue->queue_depend; + $status .= ' (waiting for '. + join(', ', map { $_->depend_jobnum } + @queue_depend + ). + ')' + if @queue_depend; + my $changable = $dangerous + || ( ! $noactions + && $status =~ /^failed/ + || $status =~ /^locked/ + ); + if ( $changable ) { + $status .= + qq! ( <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!. + qq! <A HREF="$p/misc/queue.cgi?jobnum=$jobnum&action=del">remove</A> )!; + } + $status; + }, + sub { + my $queue = shift; + # return '' if $hashref->{'svcnum'} + my $cust_svc = $queue->cust_svc; + my $account; + if ( $cust_svc ) { + my $table = $cust_svc->part_svc->svcdb; + my $label = ( $cust_svc->label )[1]; + qq!<A HREF="../view/$table.cgi?!. $queue->svcnum. + qq!">$label</A>!; + } else { + ''; + } + }, + sub { + my $queue = shift; + my $jobnum = $queue->jobnum; + my $status = $queue->status; + my $changable = $dangerous + || ( ! $noactions + && $status eq 'failed' + || $status eq 'locked' + ); + if ( $changable ) { + $areboxes = 1; + qq!<INPUT NAME="jobnum$jobnum" TYPE="checkbox" VALUE="1">!; + } else { + ''; + } + }, + ], + #'links' => [ + # '', + # '', + # '', + # '', + # '', + # '', #$acct_link, + # '', + # ], + 'html_foot' => sub { + if ( $areboxes ) { + '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'. + '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'. + '<BR><INPUT TYPE="submit" NAME="action" VALUE="retry selected">'. + '<INPUT TYPE="submit" NAME="action" VALUE="remove selected"><BR>'. + '<SCRIPT TYPE="text/javascript">'. + ' function setAll(setTo) { '. + ' theForm = document.jobForm;'. + ' for (i=0,n=theForm.elements.length;i<n;i++)'. + ' if (theForm.elements[i].name.indexOf("jobnum") != -1)'. + ' theForm.elements[i].checked = setTo;'. + ' }'. + '</SCRIPT>'; + } else { + ''; + } + }, + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Job queue'); + +my $hashref = {}; + +my $conf = new FS::Conf; +my $dangerous = $conf->exists('queue_dangerous_controls'); + +my $noactions = 0; + +my $count_query = 'SELECT COUNT(*) FROM queue'; # + $hashref + +my $areboxes = 0; + +</%init> diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html new file mode 100644 index 000000000..f65b00d05 --- /dev/null +++ b/httemplate/search/reg_code.html @@ -0,0 +1,40 @@ +<% include( 'elements/search.html', + 'title' => 'Unused Registration Codes for '. + $agent->agent, + 'name' => 'registration codes', + 'query' => { 'table' => 'reg_code', + 'hashref' => { 'agentnum' => $agentnum, }, + }, + 'count_query' => $count_query, + #'redirect' => $link, + 'header' => [ qw(Code Packages) ], + 'fields' => [ + 'code', + sub { + map { + qq!<A HREF="${p}edit/part_pkg.cgi?!. $_->pkgpart. '">'. + $_->pkg. ' - '. $_->comment. + '</A><BR>' + } $_[0]->part_pkg + }, + ], + 'links' => [ + '', + #$plink, + '', + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $agentnum = $cgi->param('agentnum'); +$agentnum =~ /^(\d+)$/ or errorpage("illegal agentnum $agentnum"); +$agentnum = $1; +my $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + +my $count_query = "SELECT COUNT(*) FROM reg_code WHERE agentnum = $agentnum"; + +</%init> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html new file mode 100644 index 000000000..06f0b1aae --- /dev/null +++ b/httemplate/search/report_cdr.html @@ -0,0 +1,44 @@ +<% include('/elements/header.html', 'Call Detail Record Search' ) %> + +<FORM ACTION="cdr.html" METHOD="GET"> + +<TABLE> + <TR> + <TD ALIGN="right">Status: </TD> + <TD> + <SELECT NAME="freesidestatus"> + <OPTION VALUE="">(all) + <OPTION VALUE="NULL">unprocessed + <OPTION VALUE="done">processed + </SELECT> + </TD> + </TR> + + <% include ( '/elements/tr-input-beginning_ending.html' ) %> + + <TR> + <TD ALIGN="right">Source #: </TD> + <TD> + <INPUT TYPE="text" NAME="src"> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Destination #: </TD> + <TD> + <INPUT TYPE="text" NAME="dst"> + </TD> + </TR> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Search Call Detail Records"> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +</%init> diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html new file mode 100644 index 000000000..892f5c4d9 --- /dev/null +++ b/httemplate/search/report_cust_bill.html @@ -0,0 +1,34 @@ +<% include('/elements/header.html', 'Invoice report criteria' ) %> + +<FORM ACTION="cust_bill.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => 'Invoices for agent: ', + ) + %> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD> + <TD>Show only open invoices</TD> + </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="newest_percust" VALUE="1"></TD> + <TD>Show only the single most recent invoice per-customer</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); + +</%init> diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html new file mode 100644 index 000000000..be02e9fbd --- /dev/null +++ b/httemplate/search/report_cust_credit.html @@ -0,0 +1,47 @@ +<% include('/elements/header.html', 'Credit report' ) %> + +<FORM ACTION="cust_credit.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + + <% include( '/elements/tr-select-otaker.html', + 'label' => 'Credits by employee: ', + 'otakers' => \@otakers, + ) + %> + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <% include( '/elements/tr-input-lessthan_greaterthan.html', + 'label' => 'Amount', + 'field' => 'amount', + ) + %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_credit") + or die dbh->errstr; +$sth->execute or die $sth->errstr; +my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; + +</%init> diff --git a/httemplate/search/report_cust_event.html b/httemplate/search/report_cust_event.html new file mode 100644 index 000000000..c1f9edb2b --- /dev/null +++ b/httemplate/search/report_cust_event.html @@ -0,0 +1,65 @@ +<% include( + '/elements/header.html', + ( $cgi->param('failed') ? 'Failed billing events' : 'Billing events' ), + ) +%> + + <FORM ACTION="cust_event.html" METHOD="GET"> + <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') ? 1 : 0 %>"> + <TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <!--<TR> + <TD ALIGN="right">Customer type</TD> + <TD><SELECT MULTIPLE NAME="perhaps_payby"> + <OPTION SELECTED VALUE="CARD">Credit card (automatic) + <OPTION SELECTED VALUE="CHEK">E-check (automatic) + <OPTION SELECTED VALUE="LECB">Phone bill billing + <OPTION SELECTED VALUE="BILL">Billing + <OPTION SELECTED VALUE="DCRD">Credit card (on-demand) + <OPTION SELECTED VALUE="DCHK">E-check (on-demand) + </TD> + </TR> + --> + <% include( '/elements/tr-input-beginning_ending.html' ) %> + <!-- + <TR> + <TD ALIGN="right">Events: </TD> + <TD> + <SELECT NAME="eventpart"> + <OPTION SELECTED VALUE=""><% $cgi->param('failed') ? '(all failed events)' : '(all events)' %> +% #foreach my $part_bill_event ( qsearch( 'part_bill_event', {} ) ) { +% #} + + </SELECT> + </TD> + </TR> + --> +<!-- <TR> + <TD ALIGN="right">Events for payment type: </TD> + <TD> + <SELECT NAME="part_bill_event.payby"> + <OPTION SELECTED VALUE="">(all) + <OPTION VALUE="CARD">Credit card (automatic) + <OPTION VALUE="BILL">Billing + <OPTION VALUE="CHEK">Electronic check (automatic) + <OPTION VALUE="DCRD">Credit card (on-demand) + <OPTION VALUE="DCHK">Electronic check (on-demand) + <OPTION VALUE="LECB">Phone bill billing + <OPTION VALUE="COMP">Complimentary + </SELECT> + </TD> + </TR> +--> + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Billing event reports'); + +</%init> diff --git a/httemplate/search/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html new file mode 100644 index 000000000..19b7ecc91 --- /dev/null +++ b/httemplate/search/report_cust_main-zip.html @@ -0,0 +1,52 @@ +<% include('/elements/header.html', 'Zip code report') %> + + <FORM ACTION="cust_main-zip.html" METHOD="GET"> + + <TABLE> + + <TR> + <TD ALIGN="right">Billing or service zip</TD> + <TD> + <SELECT NAME="column"> + <OPTION VALUE="zip">Billing zip + <OPTION VALUE="ship_zip">Service zip + </SELECT> + </TD> + </TR> + + <TR> + <TD ALIGN="right">Ignore +4 for US zip codes</TD> + <TD><INPUT TYPE="checkbox" NAME="ignore_plus4" VALUE="yes" CHECKED> </TD> + </TR> + + <TR> + <TD ALIGN="right">Show customers with status:</TD> + <TD> + <SELECT NAME="status"> + <OPTION VALUE="">all + <OPTION VALUE="prospect">prospect (no packages ever) + <OPTION SELECTED VALUE="uncancel">all except cancelled + <OPTION VALUE="active">active recurring packages + <OPTION VALUE="susp">suspended + <OPTION VALUE="cancel">cancelled + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => 'For agent: ', + ) + %> + + </TABLE> + <BR><INPUT TYPE="submit" VALUE="Get Report"> + </FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List zip codes'); + +</%init> diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html new file mode 100755 index 000000000..758c26923 --- /dev/null +++ b/httemplate/search/report_cust_main.html @@ -0,0 +1,82 @@ +<% include('/elements/header.html', 'Customer Report' ) %> + +<FORM ACTION="cust_main.html" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="bill"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH> + </TR> + <% include( '/elements/tr-select-agent.html', + ($cgi->param('agentnum') || ''), + ) + %> + +% foreach my $field (qw( signupdate )) { + + <TR> + <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD> + <TD> + <TABLE> + <% include( '/elements/tr-input-beginning_ending.html', + prefix => $field, + layout => 'horiz', + ) + %> + </TABLE> + </TD> + </TR> + +% } + + <% include( '/elements/tr-select-payby.html', + 'payby_type' => 'cust', + 'multiple' => 1, + 'curr_value' => { map { $_ => 1 } FS::payby->cust_payby }, + ) + %> + + <% include( '/elements/tr-input-lessthan_greaterthan.html', + label => 'Current balance', + field => 'current_balance', + ) + %> + + <TR> + <TD ALIGN="right" VALIGN="center">Include cancelled packages</TD> + <TD><INPUT TYPE="checkbox" NAME="cancelled_pkgs"></TD> + </TR> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH> + </TR> + <% include( '/elements/tr-select-cust-fields.html' ) %> + + <TR> + <TD ALIGN="right" VALIGN="center">Add package columns</TD> + <TD><INPUT TYPE="checkbox" NAME="flattened_pkgs"></TD> + </TR> + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') && + $FS::CurrentUser::CurrentUser->access_right('List packages') + );; + +</%init> +<%once> + +my %label = ( + 'signupdate' => 'Signup date', +); + +</%once> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html new file mode 100644 index 000000000..d7cb0aec9 --- /dev/null +++ b/httemplate/search/report_cust_pay.html @@ -0,0 +1,78 @@ +<% include('/elements/header.html', 'Payment report' ) %> + +<FORM ACTION="cust_pay.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="_date"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby" onChange="payby_changed(this)"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card (all)</OPTION> + <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION> + <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION> + <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION> + <OPTION VALUE="CARD-Maestro">credit card (Maestro/Switch/Solo)</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + <OPTION VALUE="BILL">check</OPTION> + <OPTION VALUE="PREP">prepaid card</OPTION> + <OPTION VALUE="CASH">cash</OPTION> + <OPTION VALUE="WEST">Western Union</OPTION> + <OPTION VALUE="MCRD">manual credit card</OPTION> + </SELECT> + </TD> + </TR> + + <SCRIPT TYPE="text/javascript"> + + function payby_changed(what) { + if ( what.options[what.selectedIndex].value == 'BILL' ) { + document.getElementById('checkno_caption').style.color = '#000000'; + what.form.payinfo.disabled = false; + what.form.payinfo.style.backgroundColor = '#ffffff'; + } else { + document.getElementById('checkno_caption').style.color = '#bbbbbb'; + what.form.payinfo.disabled = true; + what.form.payinfo.style.backgroundColor = '#dddddd'; + } + } + + </SCRIPT> + + <TR> + <TD ALIGN="right"><FONT ID="checkno_caption" COLOR="#bbbbbb">Check #: </FONT></TD> + <TD> + <INPUT TYPE="text" NAME="payinfo" DISABLED STYLE="background-color: #dddddd"> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar($cgi->param('agentnum')), + 'label' => 'for agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <% include( '/elements/tr-input-lessthan_greaterthan.html', + 'label' => 'Amount', + 'field' => 'paid', + ) + %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</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_pay_batch.html b/httemplate/search/report_cust_pay_batch.html new file mode 100644 index 000000000..0cd656c49 --- /dev/null +++ b/httemplate/search/report_cust_pay_batch.html @@ -0,0 +1,43 @@ +<% include('/elements/header.html', 'Batch payment report' ) %> + +<FORM ACTION="cust_pay_batch.cgi" METHOD="GET"> + +<TABLE> + + <TR> + <TD ALIGN="right">Payments of type: </TD> + <TD> + <SELECT NAME="payby"> + <OPTION VALUE="">all</OPTION> + <OPTION VALUE="CARD">credit card</OPTION> + <OPTION VALUE="CHEK">electronic check / ACH</OPTION> + </SELECT> + </TD> + </TR> + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => 'For agent: ', + ) + %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="dcln" VALUE="1" CHECKED></TD> + <TD>Include approved items</TD> + </TR> +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html new file mode 100755 index 000000000..d210446b4 --- /dev/null +++ b/httemplate/search/report_cust_pkg.html @@ -0,0 +1,148 @@ +<% include('/elements/header.html', 'Package Report' ) %> + +<FORM ACTION="cust_pkg.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="bill"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"> + <FONT SIZE="+1">Search options</FONT> + </TH> + </TR> + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar( $cgi->param('agentnum') ), + ) + %> + + <% include( '/elements/tr-select-cust_pkg-status.html', + 'onchange' => 'status_changed(this);', + ) + %> + + <SCRIPT TYPE="text/javascript"> + + function status_changed(what) { + +% foreach my $status ( '', FS::cust_pkg->statuses() ) { + + if ( what.options[what.selectedIndex].value == '<% $status %>' ) { + +% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { +% if ( $disable{$status}->{$field} ) { + + what.form.<% $field %>_beginning_text.disabled = true; + what.form.<% $field %>_ending_text.disabled = true; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#dddddd'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#dddddd'; + + what.form.<% $field %>_beginning_button.style.display = 'none'; + what.form.<% $field %>_ending_button.style.display = 'none'; + what.form.<% $field %>_beginning_disabled.style.display = ''; + what.form.<% $field %>_ending_disabled.style.display = ''; + +% } else { + + what.form.<% $field %>_beginning_text.disabled = false; + what.form.<% $field %>_ending_text.disabled = false; + what.form.<% $field %>_beginning_text.style.backgroundColor = '#ffffff'; + what.form.<% $field %>_ending_text.style.backgroundColor = '#ffffff'; + + what.form.<% $field %>_beginning_button.style.display = ''; + what.form.<% $field %>_ending_button.style.display = ''; + what.form.<% $field %>_beginning_disabled.style.display = 'none'; + what.form.<% $field %>_ending_disabled.style.display = 'none'; + +% } +% } + + } + +% } + + } + + </SCRIPT> + + <% include( '/elements/tr-select-pkg_class.html', + 'pre_options' => [ '0' => 'all' ], + 'empty_label' => '(empty class)', + ) + %> + +% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) { + + <TR> + <TD ALIGN="right" VALIGN="center"><% $label{$field} %></TD> + <TD> + <TABLE> + <% include( '/elements/tr-input-beginning_ending.html', + prefix => $field, + layout => 'horiz', + ) + %> + </TABLE> + </TD> + </TR> + +% } + + <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2> </TH> + </TR> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH> + </TR> + <% include( '/elements/tr-select-cust-fields.html' ) %> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); + +</%init> +<%once> + +my %label = ( + 'setup' => 'Setup', + 'last_bill' => 'Last bill', + 'bill' => 'Next bill', + 'adjourn' => 'Adjourns', + 'susp' => 'Suspended', + 'expire' => 'Expires', + 'cancel' => 'Cancelled', +); + +#false laziness w/cust_pkg.cgi +my %disable = ( + 'all' => {}, + 'one-time charge' => { 'last_bill'=>1, 'bill'=>1, 'adjourn'=>1, 'susp'=>1, 'expire'=>1, 'cancel'=>1, }, + 'active' => { 'susp'=>1, 'cancel'=>1 }, + 'suspended' => { 'cancel' => 1 }, + 'cancelled' => {}, + '' => {}, +); + +#hmm? +my %checkbox = ( + 'setup' => 0, + 'last_bill' => 0, + 'bill' => 0, + 'susp' => 1, + 'expire' => 1, + 'cancel' => 1, +); + +</%once> diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi new file mode 100644 index 000000000..27dbcbf9f --- /dev/null +++ b/httemplate/search/report_prepaid_income.cgi @@ -0,0 +1,87 @@ +<% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report') %> + +<% table() %> + <TR> + <TH>Actual Unearned Revenue</TH> + <TH>Legacy Unearned Revenue</TH> + </TR> + <TR> + <TD ALIGN="right">$<% $total %> + <TD ALIGN="right"> + <% $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%> + </TD> + </TR> + +</TABLE> +<BR> +Actual unearned revenue is the amount of unearned revenue Freeside has +actually invoiced for packages with longer-than monthly terms. +<BR><BR> +Legacy unearned revenue is the amount of unearned revenue represented by +customer packages. This number may be larger than actual unearned +revenue if you have imported longer-than monthly customer packages from +a previous billing system. +</BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +#doesn't yet deal with daily/weekly packages + +#needs to be re-written in sql for efficiency + +my $time = time; + +my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time; +$now =~ /^(\d+)$/ or die "unparsable date?"; +$now = $1; + +my( $total, $total_legacy ) = ( 0, 0 ); + +my @cust_bill_pkg = + grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ } + qsearch( 'cust_bill_pkg', { + 'recur' => { op=>'!=', value=>0 }, + 'edate' => { op=>'>', value=>$now }, + }, ); + +my @cust_pkg = + grep { $_->part_pkg->recur != 0 + && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/ + } + qsearch ( 'cust_pkg', { + 'bill' => { op=>'>', value=>$now } + } ); + +foreach my $cust_bill_pkg ( @cust_bill_pkg) { + my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate; + + my $elapsed = $now - $cust_bill_pkg->sdate; + $elapsed = 0 if $elapsed < 0; + + my $remaining = 1 - $elapsed/$period; + + my $unearned = $remaining * $cust_bill_pkg->recur; + $total += $unearned; + +} + +foreach my $cust_pkg ( @cust_pkg ) { + my $period = $cust_pkg->bill - $cust_pkg->last_bill; + + my $elapsed = $now - $cust_pkg->last_bill; + $elapsed = 0 if $elapsed < 0; + + my $remaining = 1 - $elapsed/$period; + + my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy + $total_legacy += $unearned; + +} + +$total = sprintf('%.2f', $total); +$total_legacy = sprintf('%.2f', $total_legacy); + +</%init> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html new file mode 100644 index 000000000..81adb64ad --- /dev/null +++ b/httemplate/search/report_prepaid_income.html @@ -0,0 +1,43 @@ +<% include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report', + '', + '', + '<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2"> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> + <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> + ' +) %> + + <FORM ACTION="report_prepaid_income.cgi" METHOD="GET"> + <TABLE> + <TR> + <TD>Prepaid income (unearned revenue) as of </TD> + <TD> + <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now"> + <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date"> + </TD> + </TR> + <TR> + <TD> + </TD> + <TD><i>m/d/y</i></TD> + </TR> + </TABLE> +<SCRIPT TYPE="text/javascript"> + Calendar.setup({ + inputField: "date_text", + ifFormat: "%m/%d/%Y", + button: "date_button", + align: "BR" + }); +</SCRIPT> + +<INPUT TYPE="submit" VALUE="Generate report"> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi new file mode 100755 index 000000000..06aea193a --- /dev/null +++ b/httemplate/search/report_receivables.cgi @@ -0,0 +1,187 @@ +<% include( 'elements/search.html', + 'title' => 'Accounts Receivable Aging Summary', + 'name' => 'customers', + 'query' => $sql_query, + 'count_query' => $count_sql, + 'header' => [ + FS::UI::Web::cust_header(), + '0-30', + '30-60', + '60-90', + '90+', + 'Total', + ], + 'footer' => [ + 'Total', + ( map '', + ( 1 .. + scalar(FS::UI::Web::cust_header()-1) + ) + ), + sprintf( $money_char.'%.2f', + $row->{'balance_0_30'} ), + sprintf( $money_char.'%.2f', + $row->{'balance_30_60'} ), + sprintf( $money_char.'%.2f', + $row->{'balance_60_90'} ), + sprintf( $money_char.'%.2f', + $row->{'balance_90_0'} ), + sprintf( '<b>'. $money_char.'%.2f'. '</b>', + $row->{'balance_0_0'} ), + ], + 'fields' => [ + \&FS::UI::Web::cust_fields, + format_balance('0_30'), + format_balance('30_60'), + format_balance('60_90'), + format_balance('90_0'), + format_balance('0_0'), + ], + 'links' => [ + ( map { $_ ne 'Cust. Status' ? $clink : '' } + FS::UI::Web::cust_header() + ), + '', + '', + '', + '', + '', + ], + #'align' => 'rlccrrrrr', + 'align' => FS::UI::Web::cust_aligns(). 'rrrrr', + #'size' => [ '', '', '-1', '-1', '', '', '', '', '', ], + #'style' => [ '', '', 'b', 'b', '', '', '', '', 'b', ], + 'size' => [ ( map '', FS::UI::Web::cust_header() ), + #'-1', '', '', '', '', '', ], + '', '', '', '', '', ], + 'style' => [ FS::UI::Web::cust_styles(), + #'b', '', '', '', '', 'b', ], + '', '', '', '', 'b', ], + 'color' => [ + FS::UI::Web::cust_colors(), + '', + '', + '', + '', + '', + ], + + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my @ranges = ( + [ 0, 30 ], + [ 30, 60 ], + [ 60, 90 ], + [ 90, 0 ], + [ 0, 0 ], +); + +my $owed_cols = join(',', map balance( @$_ ), @ranges ); + +my $select_count_pkgs = FS::cust_main->select_count_pkgs_sql; + +my $active_sql = FS::cust_pkg->active_sql; +my $inactive_sql = FS::cust_pkg->inactive_sql; +my $suspended_sql = FS::cust_pkg->suspended_sql; +my $cancelled_sql = FS::cust_pkg->cancelled_sql; + +my $packages_cols = <<END; + ( $select_count_pkgs ) AS num_pkgs_sql, + ( $select_count_pkgs AND $active_sql ) AS active_pkgs, + ( $select_count_pkgs AND $inactive_sql ) AS inactive_pkgs, + ( $select_count_pkgs AND $suspended_sql ) AS suspended_pkgs, + ( $select_count_pkgs AND $cancelled_sql ) AS cancelled_pkgs +END + +my @where = (); + +unless ( $cgi->param('all_customers') ) { + + my $days = 0; + if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) { + $days = $1; + } + + push @where, balance($days, 0, 'no_as'=>1). " > 0"; + +} + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + my $agentnum = $1; + push @where, "agentnum = $agentnum"; +} + +#here is the agent virtualization +push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $where = join(' AND ', @where); +$where = "WHERE $where" if $where; + +my $count_sql = "select count(*) from cust_main $where"; + +my $sql_query = { + 'table' => 'cust_main', + 'hashref' => {}, + 'select' => "*, $owed_cols, $packages_cols", + 'extra_sql' => $where, + 'order_by' => "order by coalesce(lower(company), ''), lower(last)", +}; + +my $join = 'LEFT JOIN cust_main USING ( custnum )'; + +my $total_sql = "SELECT ". + join(',', map balance( @$_, total=>1, join=>$join, where=>\@where ), @ranges); + +my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; +$total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr; +my $row = $total_sth->fetchrow_hashref(); + +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +</%init> +<%once> + +my $conf = new FS::Conf; + +my $money_char = $conf->config('money_char') || '$'; + +#Example: +# +# my $balance = balance( +# $start, $end, +# 'no_as' => 1, #set to true when using in a WHERE clause (supress AS clause) +# #or 0 / omit when using in a SELECT clause as a column +# # ("AS balance_$start_$end") +# #options for totals +# 'total' => 1, #set to true to remove all customer comparison clauses +# 'join' => $join, #JOIN clause +# 'where' => \@where, #WHERE clause hashref (elements "AND"ed together) +# ) + +sub balance { + my($start, $end, %opt) = @_; + + my $as = $opt{'no_as'} ? '' : " AS balance_${start}_$end"; + + #handle start and end ranges (86400 = 24h * 60m * 60s) + my $str2time = str2time_sql; + $start = $start ? "( $str2time now() ) - ".($start * 86400). ' )' : ''; + $end = $end ? "( $str2time now() ) - ".($end * 86400). ' )' : ''; + + $opt{'unapplied_date'} = 1; + + FS::cust_main->balance_date_sql( $start, $end, %opt ). $as; + +} + +sub format_balance { #closures help alot + my $range = shift; + sub { sprintf( $money_char.'%.2f', shift->get("balance_$range") ) }; +} + +</%once> diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html new file mode 100755 index 000000000..8040e57e6 --- /dev/null +++ b/httemplate/search/report_receivables.html @@ -0,0 +1,35 @@ +<% include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %> + +<FORM NAME="OneTrueForm" ACTION="report_receivables.cgi" METHOD="GET"> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"> + <FONT SIZE="+1">Search options</FONT> + </TH> + </TR> + + <% include( '/elements/tr-select-agent.html' ) %> + + <TR> + <TD ALIGN="right">Customers</TD> + <TD> + <INPUT TYPE="radio" NAME="all_customers" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">All customers (even those without an outstanding balance)<BR> + <INPUT TYPE="radio" NAME="all_customers" VALUE="0" CHECKED onClick="if ( ! this.checked ) { document.OneTrueForm.days.disabled=true; document.OneTrueForm.days.style.backgroundColor = '#dddddd'; } else { document.OneTrueForm.days.disabled=false; document.OneTrueForm.days.style.backgroundColor = '#ffffff'; }">Customers with a balance over <INPUT NAME="days" TYPE="text" SIZE=4 MAXLENGTH=3 VALUE="0"> days old + </TD> + </TR> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Get Report"> +</FORM> + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_rt_transaction.html b/httemplate/search/report_rt_transaction.html new file mode 100644 index 000000000..89629e843 --- /dev/null +++ b/httemplate/search/report_rt_transaction.html @@ -0,0 +1,22 @@ +<% include('/elements/header.html', 'Time worked report criteria' ) %> + +<FORM ACTION="rt_transaction.html" METHOD="GET"> + +<TABLE> + + <% include ( '/elements/tr-input-beginning_ending.html' ) %> + + <% include ( '/elements/tr-select-otaker.html' ) %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Search"> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +</%init> diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html new file mode 100755 index 000000000..f5a0ff903 --- /dev/null +++ b/httemplate/search/report_svc_acct.html @@ -0,0 +1,91 @@ +<% include('/elements/header.html', 'Account Report' ) %> + +<FORM ACTION="svc_acct.cgi" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="advanced"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH> + </TR> + <% include( '/elements/tr-select-agent.html', + ($cgi->param('agentnum') || ''), + ) + %> + + <SCRIPT type="text/javascript"> + function toggle(what) { + label = document.getElementById (what + '_label'); + field = document.getElementById ( what + '_invert'); + if (field.value == 1) { + field.value = 0; + } else { + field.value = 1; + } + if (field.value == 1) { + label.firstChild.nodeValue = 'Did not ' + label.firstChild.nodeValue; + }else{ + text = label.firstChild.nodeValue; + label.firstChild.nodeValue = text.replace(/Did not /, ''); + } + } + </SCRIPT> +% foreach my $field (qw( last_login last_logout )) { +% my $invert = $field."_invert"; + + <TR> + <TD> + <TABLE> + <TR> + <TD ALIGN="right" VALIGN="center" ID="<% $field."_label" %>"> + <% $label{$field} %> + </TD> + <TD> + <INPUT NAME="<% $invert %>" ID="<% $invert %>" TYPE="hidden"> + <A HREF="javascript:void(0)" onClick="toggle('<% $field %>'); return false;">Invert</A> + </TD> + </TR> + </TABLE> + </TD> + <TD> + <TABLE> + <% include( '/elements/tr-input-beginning_ending.html', + prefix => $field, + layout => 'horiz', + ) + %> + </TABLE> + </TD> + </TR> + +% } + + <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %> + + <TR> + <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH> + </TR> + <% include( '/elements/tr-select-cust-fields.html' ) %> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List packages'); + +</%init> +<%once> + +my %label = ( + 'last_login' => 'Last login', + 'last_logout' => 'Last logout', +); + +</%once> diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi new file mode 100755 index 000000000..b029ec074 --- /dev/null +++ b/httemplate/search/report_tax.cgi @@ -0,0 +1,582 @@ +<% include("/elements/header.html", "$agentname Sales Tax Report - ". + ( $beginning + ? time2str('%h %o %Y ', $beginning ) + : '' + ). + 'through '. + ( $ending == 4294967295 + ? 'now' + : time2str('%h %o %Y', $ending ) + ) + ) +%> + +<% include('/elements/table-grid.html') %> + + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Rate</TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH> + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax owed</TH> +% unless ( $cgi->param('show_taxclasses') ) { + + <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Tax invoiced</TH> +% } + + </TR> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc">Total</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt customer)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(tax-exempt package)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Non-taxable<BR><FONT SIZE=-1>(monthly exemption)</FONT></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Taxable</TH> + </TR> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; +% +% foreach my $region ( @regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% } +% +% +% +% +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1"><% $money_char %><% sprintf('%.2f', $region->{'total'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_cust'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_pkg'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $exemptlink. $link %>"><% $money_char %><% sprintf('%.2f', $region->{'exempt_monthly'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'taxable'} ) %></A> + </TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $region->{'rate'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <% $money_char %><% sprintf('%.2f', $region->{'owed'} ) %> + </TD> +% unless ( $cgi->param('show_taxclasses') ) { + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> +% } + + </TR> +% } + + +</TABLE> +% if ( $cgi->param('show_taxclasses') ) { + + + <BR> + <% include('/elements/table-grid.html') %> + <TR> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH> + </TR> +% #some false laziness w/above +% $bgcolor1 = '#eeeeee'; +% $bgcolor2 = '#ffffff'; +% foreach my $region ( @base_regions ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $link = ''; +% #if ( $region->{'label'} ne 'Total' ) { +% if ( $region->{'label'} eq $out ) { +% $link = ';out=1'; +% } else { +% $link = ';'. $region->{'url_param'}; +% } +% #} +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A> + </TD> + </TR> +% } +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% + + + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">Total</TD> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"> + <A HREF="<% $baselink %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax ) %></A> + </TD> + </TR> + + </TABLE> +% } + + +</BODY> +</HTML> +<%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 $user = getotaker; + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; +my $from_join_cust = " + FROM cust_bill_pkg + $join_cust +"; +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $where = "WHERE _date >= $beginning AND _date <= $ending "; +my @base_param = qw( county county state state country ); +if ( $conf->exists('tax-ship_address') ) { + + $where .= " + AND ( ( ( ship_last IS NULL OR ship_last = '' ) + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + ) + OR ( ship_last IS NOT NULL AND ship_last != '' + AND ( ship_county = ? OR ? = '' ) + AND ( ship_state = ? OR ? = '' ) + AND ship_country = ? + ) + ) + "; + # AND payby != 'COMP' + + push @base_param, @base_param; + +} else { + + $where .= " + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + "; + # AND payby != 'COMP' + +} + +my $agentname = ''; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + my $agent = qsearchs('agent', { 'agentnum' => $1 } ); + die "agent not found" unless $agent; + $agentname = $agent->agent; + $where .= ' AND cust_main.agentnum = '. $agent->agentnum; +} + +my $gotcust = " + WHERE 0 < ( SELECT COUNT(*) FROM cust_main +"; +if ( $conf->exists('tax-ship_address') ) { + + $gotcust .= " + WHERE + + ( cust_main_county.country = cust_main.country + OR cust_main_county.country = cust_main.ship_country + ) + + AND + + ( + + ( ( ship_last IS NULL OR ship_last = '' ) + AND ( cust_main_county.country = cust_main.country ) + AND ( cust_main_county.state = cust_main.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + OR + + ( ship_last IS NOT NULL AND ship_last != '' + AND ( cust_main_county.country = cust_main.ship_country ) + AND ( cust_main_county.state = cust_main.ship_state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.ship_county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + ) + + LIMIT 1 + ) + "; + +} else { + + $gotcust .= " + WHERE ( cust_main.county = cust_main_county.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + AND ( cust_main.state = cust_main_county.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main.country = cust_main_county.country ) + LIMIT 1 + ) + "; + +} + +my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 ); +my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 ); +my $out = 'Out of taxable region(s)'; +my %regions = (); +foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { + #warn $r->county. ' '. $r->state. ' '. $r->country. "\n"; + + my $label = getlabel($r); + $regions{$label}->{'label'} = $label; + $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) ); + + my @param = @base_param; + my $mywhere = $where; + + if ( $r->taxclass ) { + + $mywhere .= " AND taxclass = ? "; + push @param, 'taxclass'; + $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass + if $cgi->param('show_taxclasses'); + + } else { + + my $same_query = 'SELECT taxclass FROM cust_main_county '. + ' WHERE taxnum != ? AND country = ?'; + my @same_param = ( 'taxnum', 'country' ); + foreach my $opt_field (qw( state county )) { + if ( $r->$opt_field() ) { + $same_query .= " AND $opt_field = ?"; + push @same_param, $opt_field; + } else { + $same_query .= " AND $opt_field IS NULL"; + } + } + + my @taxclasses = list_sql( $r, \@same_param, $same_query ); + + if ( scalar(@taxclasses) ) { + $mywhere .= ' AND '. join(' AND ', map ' taxclass != ? ', @taxclasses ); + push @param, @taxclasses; + } + + } + + my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' "; + +# my $label = getlabel($r); +# $regions{$label}->{'label'} = $label; + + my $nottax = 'pkgnum != 0'; + + ## calculate total for this region + + my $t = scalar_sql($r, \@param, + "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax" + ); + $total += $t; + $regions{$label}->{'total'} += $t; + + ## calculate customer-exemption for this region + +## my $taxable = $t; + +# my($taxable, $x_cust) = (0, 0); +# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } +# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { +# $taxable += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" +# ); +# +# $x_cust += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" +# ); +# } + + my $x_cust = scalar_sql($r, \@param, + "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) + $fromwhere AND $nottax AND tax = 'Y' " + ); + + $exempt_cust += $x_cust; + $regions{$label}->{'exempt_cust'} += $x_cust; + + ## calculate package-exemption for this region + + my $x_pkg = scalar_sql($r, \@param, + "SELECT SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + $fromwhere + AND $nottax + AND ( + ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) + OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) + ) + AND ( tax != 'Y' OR tax IS NULL ) + " + ); + $exempt_pkg += $x_pkg; + $regions{$label}->{'exempt_pkg'} += $x_pkg; + + ## calculate monthly exemption (texas tax) for this region + + # count up all the cust_tax_exempt_pkg records associated with + # the actual line items. + + my $x_monthly = scalar_sql($r, \@param, + "SELECT SUM(amount) + FROM cust_tax_exempt_pkg + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust $join_pkg + $mywhere" + ); +# if ( $x_monthly ) { +# #warn $r->taxnum(). ": $x_monthly\n"; +# $taxable -= $x_monthly; +# } + + $exempt_monthly += $x_monthly; + $regions{$label}->{'exempt_monthly'} += $x_monthly; + + my $taxable = $t - $x_cust - $x_pkg - $x_monthly; + + $tot_taxable += $taxable; + $regions{$label}->{'taxable'} += $taxable; + + $owed += $taxable * ($r->tax/100); + $regions{$label}->{'owed'} += $taxable * ($r->tax/100); + + if ( defined($regions{$label}->{'rate'}) + && $regions{$label}->{'rate'} != $r->tax.'%' ) { + $regions{$label}->{'rate'} = 'variable'; + } else { + $regions{$label}->{'rate'} = $r->tax.'%'; + } + +} + +my $taxwhere = "$from_join_cust $where AND payby != 'COMP' "; +my @taxparam = @base_param; +my %base_regions = (); +#foreach my $label ( keys %regions ) { +foreach my $r ( + qsearch( 'cust_main_county', + {}, + "DISTINCT + country, + state, + county, + CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname,". + + #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" + , + $gotcust + ) +) { + + #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n"; + + my $label = getlabel($r); + + #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; + #my @param = @base_param; + + #match itemdesc if necessary! + my $named_tax = + $r->taxname + ? 'AND itemdesc = '. dbh->quote($r->taxname) + : "AND ( itemdesc IS NULL OR itemdesc = '' OR itemdesc = 'Tax' )"; + + my $sql = "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) ". + " $taxwhere AND pkgnum = 0 $named_tax"; + + my $x = scalar_sql($r, \@taxparam, $sql ); + $tax += $x; + $regions{$label}->{'tax'} += $x; + + if ( $cgi->param('show_taxclasses') ) { + my $base_label = getlabel($r, 'no_taxclass'=>1 ); + $base_regions{$base_label}->{'label'} = $base_label; + $base_regions{$base_label}->{'url_param'} = + join(';', map "$_=".$r->$_(), qw( county state country ) ); + $base_regions{$base_label}->{'tax'} += $x; + } + +} + +#ordering +my @regions = + map $regions{$_}, + sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } + keys %regions; + +my @base_regions = + map $base_regions{$_}, + sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) } + keys %base_regions; + +push @regions, { + 'label' => 'Total', + 'url_param' => '', + 'total' => $total, + 'exempt_cust' => $exempt_cust, + 'exempt_pkg' => $exempt_pkg, + 'exempt_monthly' => $exempt_monthly, + 'taxable' => $tot_taxable, + 'rate' => '', + 'owed' => $owed, + 'tax' => $tax, +}; + +#-- + +sub getlabel { + my $r = shift; + my %opt = @_; + + my $label; + if ( + $r->tax == 0 + && ! scalar( qsearch('cust_main_county', { 'state' => $r->state, + 'county' => $r->county, + 'country' => $r->country, + 'tax' => { op=>'>', value=>0 }, + } + ) + ) + + ) { + #kludge to avoid "will not stay shared" warning + my $out = 'Out of taxable region(s)'; + $label = $out; + } elsif ( $r->taxname ) { + $label = $r->taxname; +# $regions{$label}->{'taxname'} = $label; +# push @{$regions{$label}->{$_}}, $r->$_() foreach qw( county state country ); + } else { + $label = $r->country; + $label = $r->state.", $label" if $r->state; + $label = $r->county." county, $label" if $r->county; + $label = "$label (". $r->taxclass. ")" + if $r->taxclass + && $cgi->param('show_taxclasses') + && ! $opt{'no_taxclass'}; + #$label = $r->taxname. " ($label)" if $r->taxname; + } + return $label; +} + +#false laziness w/FS::Report::Table::Monthly (sub should probably be moved up +#to FS::Report or FS::Record or who the fuck knows where) +sub scalar_sql { + my( $r, $param, $sql ) = @_; + #warn "$sql\n"; + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute( map $r->$_(), @$param ) + or die "Unexpected error executing statement $sql: ". $sth->errstr; + $sth->fetchrow_arrayref->[0] || 0; +} + +sub list_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; + map $_->[0], @{ $sth->fetchall_arrayref }; +} + +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"; + +</%init> diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html new file mode 100755 index 000000000..35b290c19 --- /dev/null +++ b/httemplate/search/report_tax.html @@ -0,0 +1,42 @@ +<% include('/elements/header.html', 'Tax Report' ) %> + +<FORM ACTION="report_tax.cgi" METHOD="GET"> + +<TABLE> + + <% include( '/elements/tr-select-agent.html' ) %> + + <% include( '/elements/tr-input-beginning_ending.html' ) %> +% my $conf = new FS::Conf; +% if ( $conf->exists('enable_taxclasses') ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD> + <TD>Show tax classes</TD> + </TR> +% } +% my @pkg_class = qsearch('pkg_class', {}); +% if ( @pkg_class ) { +% + + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD> + <TD>Show package classes</TD> + </TR> +% } + + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html new file mode 100644 index 000000000..651f2896d --- /dev/null +++ b/httemplate/search/rt_transaction.html @@ -0,0 +1,96 @@ +<% include('elements/search.html', + 'title' => 'Time worked', + 'name_singular' => 'transaction', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $format_seconds_sub, ], + 'header' => [ 'Ticket #', + 'Ticket', + 'Date', + 'Time', + ], + 'fields' => [ 'ticketid', + sub { encode_entities(shift->get('subject')) }, + 'created', + sub { my $seconds = shift->get('transaction_time'); + &{ $format_seconds_sub }( $seconds ); + }, + ], + 'links' => [ + $link, + $link, + '', + '', + ], + ) +%> +<%once> + +my $format_seconds_sub = sub { + my $seconds = shift; + (($seconds < 0) ? '-' : '') . concise(duration($seconds)); +}; + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +#some amount of false laziness w/timeworked.html... + +my $transactiontime = " + CASE transactions.type when 'Set' + THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60 + ELSE timetaken*60 + END +"; + +my $join = 'JOIN Tickets ON Transactions.ObjectId = Tickets.Id '. + 'JOIN Users ON Transactions.Creator = Users.Id '; + +my $where = " + WHERE objecttype='RT::Ticket' + AND ( ( Transactions.Type = 'Set' + AND Transactions.Field = 'TimeWorked' + AND Transactions.NewValue != Transactions.OldValue ) + OR ( ( Transactions.Type='Create' OR Transactions.Type='Comment' OR Transactions.Type='Correspond' ) + AND Transactions.TimeTaken > 0 + ) + ) +"; +#AND transaction_time != 0 +#AND $wheretimeleft + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +# TIMESTAMP is Pg-specific... ? +if ( $beginning > 0 ) { + $beginning = "TIMESTAMP '". time2str('%Y-%m-%d %X', $beginning). "'"; + $where .= " AND Transactions.Created >= $beginning "; +} +if ( $ending < 4294967295 ) { + $ending = "TIMESTAMP '". time2str('%Y-%m-%d %X', $ending). "'"; + $where .= " AND Transactions.Created <= $ending "; +} + +if ( $cgi->param('otaker') && $cgi->param('otaker') =~ /^([\w\.\-]+)$/ ) { + $where .= " AND Users.name = '$1' "; +} + +my $query = { + 'select' => "Transactions.*, Tickets.Id AS ticketid, Tickets.Subject, Users.name as otaker, $transactiontime AS transaction_time", + #'table' => 'Transactions', + 'table' => 'transactions', + 'addl_from' => $join. + 'LEFT JOIN acct_rt_transaction '. + ' ON Transactions.Id = acct_rt_transaction.transaction_id', + 'extra_sql' => $where, + 'order by' => 'ORDER BY Created', +}; + +my $count_query = + "SELECT COUNT(*), SUM($transactiontime) FROM Transactions $join $where"; + +my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->get('id'); } ]; + +</%init> diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html new file mode 100644 index 000000000..df9b8cddb --- /dev/null +++ b/httemplate/search/sql.html @@ -0,0 +1,13 @@ +<% include( 'elements/search.html', + 'title' => 'Query Results', + 'name' => 'rows', + 'query' => 'SELECT '. ( $cgi->param('sql') + || errorpage('Empty query') ), + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Raw SQL'); + +</%init> diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi new file mode 100644 index 000000000..24b17d3b3 --- /dev/null +++ b/httemplate/search/sqlradius.cgi @@ -0,0 +1,305 @@ +<% include( '/elements/header.html', 'RADIUS Sessions') %> + +% ### +% # and finally, display the thing +% ### +% +% foreach my $part_export ( +% #grep $_->can('usage_sessions'), qsearch( 'part_export' ) +% qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ), +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ) +% ) { +% %user2svc_acct = (); +% +% my $efields = tie my %efields, 'Tie::IxHash', %fields; +% delete $efields{'framedipaddress'} if $part_export->option('hide_ip'); +% if ( $part_export->option('hide_data') ) { +% delete $efields{$_} foreach qw(acctinputoctets acctoutputoctets); +% } +% if ( $part_export->option('show_called_station') ) { +% $efields->Splice(1, 0, +% 'calledstationid' => { +% 'name' => 'Destination', +% 'attrib' => 'Called-Station-ID', +% 'fmt' => +% sub { length($_[0]) ? shift : ' '; }, +% 'align' => 'left', +% }, +% ); +% } +% +% + + <% $part_export->exporttype %> to <% $part_export->machine %><BR> + <% include( '/elements/table-grid.html' ) %> +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor; + + <TR> +% foreach my $field ( keys %efields ) { + + <TH CLASS="grid" BGCOLOR="#cccccc"> + <% $efields{$field}->{name} %><BR> + <FONT SIZE=-2><% $efields{$field}->{attrib} %></FONT> + </TH> + +% } + </TR> + +% foreach my $session ( +% @{ $part_export->usage_sessions( +% $beginning, $ending, $cgi_svc_acct, $ip, $prefix, ) } +% ) { +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } + + <TR> +% foreach my $field ( keys %efields ) { +% my $html = &{ $efields{$field}->{fmt} }( $session->{$field}, +% $session, +% $part_export, +% ); +% my $class = ( $html =~ /<TABLE/ ? 'inv' : 'grid' ); + + <TD CLASS="<%$class%>" BGCOLOR="<% $bgcolor %>" ALIGN="<% $efields{$field}->{align} %>"> + <% $html %> + </TD> +% } + </TR> + +% } + +</TABLE> +<BR><BR> + +% } + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +### +# parse cgi params +### + +#sort of false laziness w/cust_pay.cgi +my $beginning = ''; +my $ending = ''; +if ( $cgi->param('beginning') + && $cgi->param('beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $beginning = str2time($1); +} +if ( $cgi->param('ending') + && $cgi->param('ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) { + $ending = str2time($1); # + 86399; +} +if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) { + $beginning = $1; +} +if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) { + $ending = $1; +} + +my $cgi_svc_acct = ''; +if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } ); +} elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) { + my %search = { 'username' => $1 }; + my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; + } + $cgi_svc_acct = qsearchs( 'svc_acct', \%search ) + if keys %search; +} elsif ( $cgi->param('username') =~ /^(.+)$/ ) { + $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } ); +} + +my $ip = ''; +if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) { + $ip = $1; +} + +my $prefix = $cgi->param('prefix'); +$prefix =~ s/\D//g; +if ( $prefix =~ /^(\d+)$/ ) { + $prefix = $1; + $prefix = "011$prefix" unless $prefix =~ /^1/; +} else { + $prefix = ''; +} + +### +# field formatting subroutines +### + +my %user2svc_acct = (); +my $user_format = sub { + my ( $user, $session, $part_export ) = @_; + + my $svc_acct = ''; + if ( exists $user2svc_acct{$user} ) { + $svc_acct = $user2svc_acct{$user}; + } else { + my %search = (); + if ( $part_export->exporttype eq 'sqlradius_withdomain' ) { + my $domain; + if ( $user =~ /^([^@]+)\@([^@]+)$/ ) { + $search{'username'} = $1; + $domain = $2; + } else { + $search{'username'} = $user; + $domain = $session->{'realm'}; + } + my $svc_domain = qsearchs('svc_domain', { 'domain' => $domain } ); + if ( $svc_domain ) { + $search{'domsvc'} = $svc_domain->svcnum; + } else { + delete $search{'username'}; + } + } elsif ( $part_export->exporttype eq 'sqlradius' ) { + $search{'username'} = $user; + } else { + die 'unknown export type '. $part_export->exporttype. + " for $part_export\n"; + } + if ( keys %search ) { + my @svc_acct = + grep { qsearchs( 'export_svc', { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $_->cust_svc->svcpart, + } ) + } qsearch( 'svc_acct', \%search ); + if ( @svc_acct ) { + warn 'multiple svc_acct records for user $user found; '. + 'using first arbitrarily' + if scalar(@svc_acct) > 1; + $user2svc_acct{$user} = $svc_acct = shift @svc_acct; + } + } + } + + if ( $svc_acct ) { + my $svcnum = $svc_acct->svcnum; + qq(<A HREF="${p}view/svc_acct.cgi?$svcnum"><B>$user</B></A>); + } else { + "<B>$user</B>"; + } + +}; + +my $customer_format = sub { + my( $unused, $session ) = @_; + return ' ' unless exists $user2svc_acct{$session->{'username'}}; + my $svc_acct = $user2svc_acct{$session->{'username'}}; + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + return ' ' unless $cust_pkg; + my $cust_main = $cust_pkg->cust_main; + + qq!<A HREF="${p}view/cust_main.cgi?!. $cust_main->custnum. '">'. + $cust_pkg->cust_main->name. '</A>'; +}; + +my $time_format = sub { + my $time = shift; + return ' ' if $time == 0; + my $pretty = time2str('%T%P %a %b %o %Y', $time ); + $pretty =~ s/ (\d)(st|dn|rd|th)/$1$2/; + $pretty; +}; + +my $duration_format = sub { + my $seconds = shift; + my $hour = int($seconds/3600); + my $min = int( ($seconds%3600) / 60 ); + my $sec = $seconds%60; + '<TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0>'. + '<TR><TD CLASS="inv" ALIGN="right">'. + ( $hour ? "<B>$hour</B>h" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + ( ( $hour || $min ) ? "<B>$min</B>m" : ' ' ). + '</TD><TD CLASS="inv" ALIGN="right">'. + "<B>$sec</B>s". + '</TD></TR></TABLE>'; +}; + +my $octets_format = sub { + my $octets = shift; + my $megs = $octets / 1048576; + sprintf('<B>%.3f</B> megs', $megs); + #my $gigs = $octets / 1073741824 + #sprintf('<B>%.3f</B> gigabytes', $gigs); +}; + +### +# the fields +### + +tie my %fields, 'Tie::IxHash', + 'username' => { + name => 'User', + attrib => 'UserName', + fmt => $user_format, + align => 'left', + }, + 'realm' => { + name => 'Realm', + attrib => 'Realm', + align => 'left', + }, + 'dummy' => { + name => 'Customer', + attrib => '', + fmt => $customer_format, + align => 'left', + }, + 'framedipaddress' => { + name => 'IP Address', + attrib => 'Framed-IP-Address', + fmt => sub { my $ip = shift; + length($ip) ? $ip : ' '; + }, + align => 'right', + }, + 'acctstarttime' => { + name => 'Start time', + attrib => 'Acct-Start-Time', + fmt => $time_format, + align => 'left', + }, + 'acctstoptime' => { + name => 'End time', + attrib => 'Acct-Stop-Time', + fmt => $time_format, + align => 'left', + }, + 'acctsessiontime' => { + name => 'Duration', + attrib => 'Acct-Session-Time', + fmt => $duration_format, + align => 'right', + }, + 'acctinputoctets' => { + name => 'Upload', # (from user)', + attrib => 'Acct-Input-Octets', + fmt => $octets_format, + align => 'right', + }, + 'acctoutputoctets' => { + name => 'Download', # (to user)', + attrib => 'Acct-Output-Octets', + fmt => $octets_format, + align => 'right', + }, +; +$fields{$_}->{fmt} ||= sub { length($_[0]) ? shift : ' '; } + foreach keys %fields; + +</%init> diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html new file mode 100644 index 000000000..660a54f3c --- /dev/null +++ b/httemplate/search/sqlradius.html @@ -0,0 +1,59 @@ +<% include( '/elements/header.html', 'Search RADIUS sessions' ) %> + +<FORM NAME="OneTrueForm" ACTION="sqlradius.cgi" METHOD="GET"> +% #include( '/elements/table.html' ) + +<% ntable('#cccccc') %> +<TR> + <TD ALIGN="right">Username: </TD> + <TD><INPUT TYPE="text" NAME="username"></TD> +</TR> +<TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all users)</I></FONT></TD> +</TR> +% my @part_export = qsearch( 'part_export', { 'exporttype' => 'sqlradius' } ); +% push @part_export, +% qsearch( 'part_export', { 'exporttype' => 'sqlradius_withdomain' } ); +% +% if ( grep { ! $_->option('hide_ip') } @part_export ) { + + <TR> + <TD ALIGN="right">IP address: </TD> + <TD><INPUT TYPE="text" NAME="ip"></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all IPs)</I></FONT></TD> + </TR> +% } +% if ( grep { $_->option('show_called_station') } @part_export ) { + + <TR> + <TD ALIGN="right">Destination prefix:</TD> + <TD><INPUT TYPE="text" NAME="prefix"></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(country code or country code and prefix)</I></FONT></TD> + </TR> + <TR> + <TD></TD> + <TD><FONT SIZE="-1"><I>(leave blank to show all destinations)</I></FONT></TD> + </TR> +% } + + +<% include( '/elements/tr-input-beginning_ending.html', 'input_time'=>1 ) %> + +</TABLE> +<BR><INPUT TYPE="submit" VALUE="View sessions"> +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +</%init> diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi new file mode 100755 index 000000000..c2b3d8fa0 --- /dev/null +++ b/httemplate/search/svc_acct.cgi @@ -0,0 +1,289 @@ +<% include( 'elements/search.html', + 'title' => 'Account Search Results', + 'name' => 'accounts', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'align' => $align, + 'color' => \@color, + 'style' => \@style, + ) +%> +<%once> + +#false laziness w/ClientAPI/MyAccount.pm +sub format_time { + my $support = shift; + (($support < 0) ? '-' : '' ). int(abs($support)/3600)."h".sprintf("%02d",(abs($support)%3600)/60)."m"; +} + +sub timelast { + my( $svc_acct, $last, $permonth ) = @_; + + my $sql = " + SELECT SUM(support) FROM acct_rt_transaction + LEFT JOIN Transactions + ON Transactions.Id = acct_rt_transaction.transaction_id + WHERE svcnum = ? + AND Transactions.Created >= ? + "; + + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute( $svc_acct->svcnum, + time2str('%Y-%m-%d %X', time - $last*86400 ) + ) + or die $sth->errstr; + + my $seconds = $sth->fetchrow_arrayref->[0]; + + my $return = (($seconds < 0) ? '-' : '') . concise(duration($seconds)); + + $return .= sprintf(' (%.2fx)', $seconds / $permonth ) if $permonth; + + $return; + +} + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ]; +my $link_cust = sub { + my $svc_acct = shift; + if ( $svc_acct->custnum ) { + [ "${p}view/cust_main.cgi?", 'custnum' ]; + } else { + ''; + } +}; + +my @extra_sql = (); + +my @header = ( '#', 'Service', 'Account', 'UID', 'Last Login' ); +my @fields = ( 'svcnum', 'svc', 'email', 'uid', 'last_login_text' ); +my @links = ( $link, $link, $link, $link, $link ); +my $align = 'rlllr'; +my @color = ( '', '', '', '', '' ); +my @style = ( '', '', '', '', '' ); + +if ( $cgi->param('domain') ) { + my $svc_domain = + qsearchs('svc_domain', { 'domain' => $cgi->param('domain') } ); + unless ( $svc_domain ) { + #it would be nice if this looked more like the other "not found" + #errors, but this will do for now. + errorpage("Domain ". $cgi->param('domain'). " not found at all"); + } else { + push @extra_sql, 'domsvc = '. $svc_domain->svcnum; + } +} + +my $timepermonth = ''; + +my $orderby = 'ORDER BY svcnum'; +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + push @extra_sql, 'pkgnum IS NULL' + if $cgi->param('magic') eq 'unlinked'; + + my $sortby = ''; + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + $sortby = $1; + $sortby = "LOWER($sortby)" + if $sortby eq 'username'; + push @extra_sql, "$sortby IS NOT NULL" + if $sortby eq 'uid' || $sortby eq 'seconds' || $sortby eq 'last_login'; + $orderby = "ORDER BY $sortby"; + } + + if ( $sortby eq 'seconds' ) { + #push @header, 'Time remaining'; + push @header, 'Time'; + push @fields, sub { my $svc_acct = shift; format_time($svc_acct->seconds) }; + push @links, ''; + $align .= 'r'; + push @color, ''; + push @style, ''; + + my $conf = new FS::Conf; + if ( $conf->exists('svc_acct-display_paid_time_remaining') ) { + push @header, 'Paid time', 'Last 30', 'Last 60', 'Last 90'; + push @fields, + sub { + my $svc_acct = shift; + my $seconds = $svc_acct->seconds; + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + my $part_pkg = $cust_pkg->part_pkg; + #my $timepermonth = $part_pkg->option('seconds'); + $timepermonth = $part_pkg->option('seconds'); + $timepermonth = $timepermonth / $part_pkg->freq + if $part_pkg->freq =~ /^\d+$/ && $part_pkg->freq != 0; + return format_time($seconds) unless $timepermonth; + #my $recur = $part_pkg->calc_recur($cust_pkg); + my $recur = $part_pkg->base_recur($cust_pkg); + my $balance = $cust_pkg->cust_main->balance; + my $months_unpaid = $balance / $recur; + my $time_unpaid = $months_unpaid * $timepermonth; + format_time($seconds-$time_unpaid). + sprintf(' (%.2fx monthly)', ( $seconds-$time_unpaid ) / $timepermonth ); + }, + sub { timelast( shift, 30, $timepermonth ); }, + sub { timelast( shift, 60, $timepermonth ); }, + sub { timelast( shift, 90, $timepermonth ); }, + ; + push @links, '', '', '', ''; + $align .= 'rrrr'; + push @color, '', '', '', ''; + push @style, '', '', '', ''; + } + + } + +} elsif ( $cgi->param('magic') =~ /^nologin$/ ) { + + if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { + my $sortby = $1; + $sortby = "LOWER($sortby)" + if $sortby eq 'username'; + push @extra_sql, "last_login IS NULL"; + $orderby = "ORDER BY $sortby"; + } + +} elsif ( $cgi->param('magic') =~ /^advanced$/ ) { + $orderby = ""; + + if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { + push @extra_sql, "agentnum = $1"; + } + + my $pkgpart = join (' OR cust_pkg.pkgpart=', + grep {$_} map { /^(\d+)$/; } ($cgi->param('pkgpart'))); + push @extra_sql, '(cust_pkg.pkgpart=' . $pkgpart . ')' if $pkgpart; + + foreach my $field (qw( last_login last_logout )) { + + my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); + + next if $beginning == 0 && $ending == 4294967295; + + if ($cgi->param($field."_invert")) { + push @extra_sql, + "(svc_acct.$field IS NULL OR ". + "svc_acct.$field < $beginning AND ". + "svc_acct.$field > $ending)"; + } else { + push @extra_sql, + "svc_acct.$field IS NOT NULL", + "svc_acct.$field >= $beginning", + "svc_acct.$field <= $ending"; + } + + $orderby ||= "ORDER BY svc_acct.$field" . + ($cgi->param($field."_invert") ? ' DESC' : ''); + + } + + $orderby ||= "ORDER BY svcnum"; + +} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) { + push @extra_sql, "popnum = $1"; + $orderby = "ORDER BY LOWER(username)"; +} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { + push @extra_sql, "svcpart = $1"; + $orderby = "ORDER BY uid"; + #$orderby = "ORDER BY svcnum"; +} else { + $orderby = "ORDER BY uid"; + + my @username_sql; + + my %username_type; + foreach ( $cgi->param('username_type') ) { + $username_type{$_}++; + } + + $cgi->param('username') =~ /^([\w\-\.\&]+)$/; #untaint username_text + my $username = $1; + + push @username_sql, "username ILIKE '$username'" + if $username_type{'Exact'} + || $username_type{'Fuzzy'}; + + push @username_sql, "username ILIKE '\%$username\%'" + if $username_type{'Substring'} + || $username_type{'All'}; + + if ( $username_type{'Fuzzy'} || $username_type{'All'} ) { + &FS::svc_acct::check_and_rebuild_fuzzyfiles; + my $all_username = &FS::svc_acct::all_username; + + my %username; + if ( $username_type{'Fuzzy'} || $username_type{'All'} ) { + foreach ( amatch($username, [ qw(i) ], @$all_username) ) { + $username{$_}++; + } + } + + #if ($username_type{'Sound-alike'}) { + #} + + push @username_sql, "username = '$_'" + foreach (keys %username); + + } + + push @extra_sql, '( '. join( ' OR ', @username_sql). ' )'; + +} + +push @header, FS::UI::Web::cust_header($cgi->param('cust_fields')); +push @fields, \&FS::UI::Web::cust_fields, +push @links, map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header($cgi->param('cust_fields')); +$align .= FS::UI::Web::cust_aligns(); +push @color, FS::UI::Web::cust_colors(); +push @style, FS::UI::Web::cust_styles(); + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = + scalar(@extra_sql) + ? ' WHERE '. join(' AND ', @extra_sql ) + : ''; + +my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql"; +#if ( keys %svc_acct ) { +# $count_query .= ' WHERE '. +# join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}), +# keys %svc_acct +# ); +#} + +my $sql_query = { + 'table' => 'svc_acct', + 'hashref' => {}, # \%svc_acct, + 'select' => join(', ', + 'svc_acct.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +</%init> diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi new file mode 100755 index 000000000..56711615c --- /dev/null +++ b/httemplate/search/svc_broadband.cgi @@ -0,0 +1,132 @@ +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List services'); +% +%my $conf = new FS::Conf; +% +%my @svc_broadband = (); +%my $sortby=\*svcnum_sort; +%#XXX agent-virtualization needs to be finished :/ +%my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql( +% 'null_right' => 'View/link unlinked services' +% ); +% +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% @svc_broadband = qsearch( +% 'table' => 'svc_broadband', +% 'hashref' => {}, +% #needs the join first 'extra_sql' => "WHERE $agentnums_sql", +% ); +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_broadband = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_broadband; +% } else { +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'blocknum' ) { +% $sortby = \*blocknum_sort; +% } +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% +% @svc_broadband = +% qsearch( { +% 'table' => 'svc_broadband', +% 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )', +% 'extra_sql' => "WHERE svcpart = $1", +% } +% ); +% +%} elsif ( $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { +% my $ip_addr = $1; +% @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr}); +%} +% +%my %routerbyblock = (); +%foreach my $router (qsearch('router', {})) { +% foreach ($router->addr_block) { +% $routerbyblock{$_->blocknum} = $router; +% } +%} +% +%if ( scalar(@svc_broadband) == 1 ) { +% print $cgi->redirect(popurl(2). "view/svc_broadband.cgi?". $svc_broadband[0]->svcnum); +% #exit; +%} elsif ( scalar(@svc_broadband) == 0 ) { +% + +<!-- mason kludge --> +% +% errorpage("No matching broadband services found!"); +%} else { +% + +<!-- mason kludge --> +% +% my($total)=scalar(@svc_broadband); +% print header("Broadband Search Results",''), <<END; +% +% $total matching broadband services found +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Service #</TH> +% <TH>Router</TH> +% <TH>IP Address</TH> +% </TR> +%END +% +% foreach my $svc_broadband ( +% sort $sortby (@svc_broadband) +% ) { +% my($svcnum,$ip_addr,$routername,$routernum)=( +% $svc_broadband->svcnum, +% $svc_broadband->ip_addr, +% $routerbyblock{$svc_broadband->blocknum}->routername, +% $routerbyblock{$svc_broadband->blocknum}->routernum, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/router.cgi?$routernum">$routername</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$ip_addr</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub blocknum_sort { +% if ($a->getfield('blocknum') == $b->getfield('blocknum')) { +% $a->getfield('ip_addr') cmp $b->getfield('ip_addr'); +% } else { +% $a->getfield('blocknum') cmp $b->getfield('blocknum'); +% } +%} +% +% +% + diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi new file mode 100755 index 000000000..08ffdba5e --- /dev/null +++ b/httemplate/search/svc_domain.cgi @@ -0,0 +1,112 @@ +<% include( 'elements/search.html', + 'title' => "Domain Search Results", + 'name' => 'domains', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Domain', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'domain', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my %svc_domain = (); +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + 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 { + $cgi->param('domain') =~ /^([\w\-\.]+)$/; + $svc_domain{'domain'} = $1; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = ''; +if ( @extra_sql ) { + $extra_sql = ( keys(%svc_domain) ? ' AND ' : ' WHERE ' ). + join(' AND ', @extra_sql ); +} + +my $count_query = "SELECT COUNT(*) FROM svc_domain $addl_from "; +if ( keys %svc_domain ) { + $count_query .= ' WHERE '. + join(' AND ', map "$_ = ". dbh->quote($svc_domain{$_}), + keys %svc_domain + ); +} +$count_query .= $extra_sql; + +my $sql_query = { + 'table' => 'svc_domain', + 'hashref' => \%svc_domain, + 'select' => join(', ', + 'svc_domain.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_domain.cgi?", 'svcnum' ]; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi new file mode 100755 index 000000000..2710d75bc --- /dev/null +++ b/httemplate/search/svc_external.cgi @@ -0,0 +1,153 @@ +%die "access denied" +% unless $FS::CurrentUser::CurrentUser->access_right('List services'); +% +%my $conf = new FS::Conf; +% +%my @svc_external = (); +%my @h_svc_external = (); +%my $sortby=\*svcnum_sort; +%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { +% +% @svc_external=qsearch('svc_external',{}); +% +% if ( $cgi->param('magic') eq 'unlinked' ) { +% @svc_external = grep { qsearchs('cust_svc', { +% 'svcnum' => $_->svcnum, +% 'pkgnum' => '', +% } +% ) +% } +% @svc_external; +% } +% +% if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { +% my $sortby = $1; +% if ( $sortby eq 'id' ) { +% $sortby = \*id_sort; +% } +% } +% +%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { +% +% @svc_external = +% qsearch( 'svc_external', {}, '', +% " WHERE $1 = ( SELECT svcpart FROM cust_svc ". +% " WHERE cust_svc.svcnum = svc_external.svcnum ) " +% ); +% +%} elsif ( $cgi->param('title') =~ /^(.*)$/ ) { +% $sortby=\*id_sort; +% @svc_external=qsearch('svc_external',{ title => $1 }); +% if( $cgi->param('history') == 1 ) { +% @h_svc_external=qsearch('h_svc_external',{ title => $1 }); +% } +%} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) { +% my $id = $1; +% @svc_external = qsearchs('svc_external',{'id'=>$id}); +%} +% +%if ( scalar(@svc_external) == 1 ) { +% +% +<% $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %> +% +% +%} elsif ( scalar(@svc_external) == 0 ) { +% +% +<% include('/elements/header.html', 'External Search Results' ) %> + + No matching external services found +% } else { +% +% +<% include('/elements/header.html', 'External Search Results', '') %> + + <% scalar(@svc_external) %> matching external services found + <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> + <TR> + <TH>Service #</TH> + <TH><% FS::Msgcat::_gettext('svc_external-id') || 'External ID' %></TH> + <TH><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH> + </TR> +% +% foreach my $svc_external ( +% sort $sortby (@svc_external) +% ) { +% my($svcnum, $id, $title)=( +% $svc_external->svcnum, +% $svc_external->id, +% $svc_external->title, +% ); +% +% my $rowspan = 1; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD> +%END +% +% #print @rows; +% print "</TR>"; +% +% } +% if( scalar(@h_svc_external) > 0 ) { +% print <<HTML; +% </TABLE> +% <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +% <TR> +% <TH>Freeside ID</TH> +% <TH>Service #</TH> +% <TH>Title</TH> +% <TH>Date</TH> +% </TR> +%HTML +% +% foreach my $h_svc ( @h_svc_external ) { +% my($svcnum, $id, $title, $user, $date)=( +% $h_svc->svcnum, +% $h_svc->id, +% $h_svc->title, +% $h_svc->history_user, +% $h_svc->history_date, +% ); +% my $rowspan = 1; +% my ($h_cust_svc) = qsearchs( 'h_cust_svc', { +% svcnum => $svcnum, +% }); +% my $cust_pkg = qsearchs( 'cust_pkg', { +% pkgnum => $h_cust_svc->pkgnum, +% }); +% my $custnum = $cust_pkg->custnum; +% +% print <<END; +% <TR> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$svcnum</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$title</A></TD> +% <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$date</A></TD> +% </TR> +%END +% } +% } +% +% print <<END; +% </TABLE> +% </BODY> +%</HTML> +%END +% +%} +% +%sub svcnum_sort { +% $a->getfield('svcnum') <=> $b->getfield('svcnum'); +%} +% +%sub id_sort { +% $a->getfield('id') <=> $b->getfield('id'); +%} +% +% + diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi new file mode 100755 index 000000000..2bcd0c8c8 --- /dev/null +++ b/httemplate/search/svc_forward.cgi @@ -0,0 +1,146 @@ +<% include( 'elements/search.html', + 'title' => "Mail forward Search Results", + 'name' => 'mail forwards', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Mail to', + 'Forwards to', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + $format_src, + $format_dst, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link_src, + $link_dst, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + 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"; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = + scalar(@extra_sql) + ? ' WHERE '. join(' AND ', @extra_sql ) + : ''; + +my $count_query = "SELECT COUNT(*) FROM svc_forward $addl_from $extra_sql"; +my $sql_query = { + 'table' => 'svc_forward', + 'hashref' => {}, + 'select' => join(', ', + 'svc_forward.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +# <TH>Service #<BR><FONT SIZE=-1>(click to view forward)</FONT></TH> +# <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH> +# <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH> + +my $link = [ "${p}view/svc_forward.cgi?", 'svcnum' ]; + +my $format_src = sub { + my $svc_forward = shift; + if ( $svc_forward->srcsvc_acct ) { + $svc_forward->srcsvc_acct->email; + } else { + my $src = $svc_forward->src; + $src = "<I>(anything)</I>$src" if $src =~ /^@/; + $src; + } +}; + +my $link_src = sub { + my $svc_forward = shift; + if ( $svc_forward->srcsvc_acct ) { + [ "${p}view/svc_acct.cgi?", 'srcsvc' ]; + } else { + ''; + } +}; + +my $format_dst = sub { + my $svc_forward = shift; + if ( $svc_forward->dstsvc_acct ) { + $svc_forward->dstsvc_acct->email; + } else { + $svc_forward->dst; + } +}; + +my $link_dst = sub { + my $svc_forward = shift; + if ( $svc_forward->dstsvc_acct ) { + [ "${p}view/svc_acct.cgi?", 'dstsvc' ]; + } else { + ''; + } +}; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi new file mode 100644 index 000000000..49340c6c3 --- /dev/null +++ b/httemplate/search/svc_phone.cgi @@ -0,0 +1,117 @@ +<% include( 'elements/search.html', + 'title' => "Phone number search results", + 'name' => 'phone numbers', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Country code', + 'Phone number', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + 'countrycode', + 'phonenum', + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + $link, + $link, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlrr'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my %svc_phone = (); +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + 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 { + $cgi->param('phonenum') =~ /^([\d\- ]+)$/; + ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = ''; +if ( @extra_sql ) { + $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ). + join(' AND ', @extra_sql ); +} + +my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from "; +if ( keys %svc_phone ) { + $count_query .= ' WHERE '. + join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}), + keys %svc_phone + ); +} +$count_query .= $extra_sql; + +my $sql_query = { + 'table' => 'svc_phone', + 'hashref' => \%svc_phone, + 'select' => join(', ', + 'svc_phone.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ]; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi new file mode 100755 index 000000000..2e3c4615b --- /dev/null +++ b/httemplate/search/svc_www.cgi @@ -0,0 +1,113 @@ +<% include( 'elements/search.html', + 'title' => 'Virtual Host Search Results', + 'name' => 'virtual hosts', + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ '#', + 'Service', + 'Zone', + 'User', + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + 'svc', + sub { $_[0]->domain_record->zone }, + sub { + my $svc_www = shift; + my $svc_acct = $svc_www->svc_acct; + $svc_acct + ? $svc_acct->email + : ''; + }, + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + '', + $ulink, + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + '', + '', + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + '', + '', + FS::UI::Web::cust_styles(), + ], + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List services'); + +#my $conf = new FS::Conf; + +my $orderby = 'ORDER BY svcnum'; +my @extra_sql = (); +if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { + + 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"; +} + +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services' + ); + +my $extra_sql = + scalar(@extra_sql) + ? ' WHERE '. join(' AND ', @extra_sql ) + : ''; + + +my $count_query = "SELECT COUNT(*) FROM svc_www $addl_from $extra_sql"; +my $sql_query = { + 'table' => 'svc_www', + 'hashref' => {}, + 'select' => join(', ', + 'svc_www.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => "$extra_sql $orderby", + 'addl_from' => $addl_from, +}; + +my $link = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +#my $dlink = [ "${p}view/svc_www.cgi?", 'svcnum', ]; +my $ulink = [ "${p}view/svc_acct.cgi?", 'usersvc', ]; + +#smaller false laziness w/svc_*.cgi here +my $link_cust = sub { + my $svc_x = shift; + $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; +}; + +</%init> diff --git a/httemplate/search/timeworked.html b/httemplate/search/timeworked.html new file mode 100644 index 000000000..b72dd0ea9 --- /dev/null +++ b/httemplate/search/timeworked.html @@ -0,0 +1,117 @@ +<% include( 'elements/search.html', + 'title' => 'Time Worked', + 'name' => 'time', + 'html_form' => qq!<FORM NAME="timeForm" ACTION="${p}misc/timeworked.html" METHOD="POST">!, + 'query' => $query, + 'count_query' => $count_query, + 'header' => [ '#', + 'Ticket', + 'Date', + 'Time', + '', # checkbox column + ], + 'fields' => [ sub { shift->[0] }, + sub { encode_entities(shift->[1]) }, + sub { shift->[2] }, + sub { my $seconds = shift->[3]; + (($seconds < 0) ? '-' : '') . + concise(duration($seconds)); + }, + sub { + my $row = shift; + my $seconds = $row->[3]; + my $id = $row->[4]; + qq!<INPUT NAME="transactionid$id" TYPE="checkbox" VALUE="1">!. + qq!<INPUT NAME="seconds$id" TYPE="hidden" VALUE="$seconds">!; + }, + ], + 'links' => [ + $link, + $link, + '', + '', + '', + ], + 'html_foot' => sub { + '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'. + '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'. + '<BR><INPUT TYPE="submit" NAME="action" VALUE="Assign to accounts"><BR>'. + '<SCRIPT TYPE="text/javascript">'. + ' function setAll(setTo) { '. + ' theForm = document.timeForm;'. + ' for (i=0,n=theForm.elements.length;i<n;i++)'. + ' if (theForm.elements[i].name.indexOf("transactionid") != -1)'. + ' theForm.elements[i].checked = setTo;'. + ' }'. + '</SCRIPT>'; + }, + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Time queue'); + +my @groupby = (); + +my $transactiontime = " + CASE Transactions.Type WHEN 'Set' + THEN (TO_NUMBER(NewValue,'999999')-TO_NUMBER(OldValue, '999999')) * 60 + ELSE TimeTaken*60 + END +"; + +push @groupby, qw( transactions.type newvalue oldvalue timetaken ); + +my $appliedtimeclause = "COALESCE (SUM(acct_rt_transaction.seconds), 0)"; + +my $appliedtimeselect = " + COALESCE( + ( SELECT SUM(seconds) FROM acct_rt_transaction + WHERE transaction_id = Transactions.id + ), + 0 + ) +"; + +push @groupby, "Transactions.id"; + +my $wheretimeleft = "$transactiontime != $appliedtimeselect"; + +push @groupby, "Tickets.id"; +push @groupby, "Tickets.Subject"; +push @groupby, "Transactions.Created"; + +my $groupby = join(',', @groupby); + +my $where = " + WHERE ObjectType='RT::Ticket' + AND ( ( Transactions.Type='Set' AND Field='TimeWorked' ) + OR Transactions.Type='Create' + OR Transactions.Type='Comment' + OR Transactions.Type='Correspond' + ) + AND $wheretimeleft +"; + #AND $wheretimeleft + +my $query = " + SELECT Tickets.id, Tickets.Subject, + TO_CHAR(Transactions.Created, 'Dy Mon DD HH24:MI:SS YYYY'), + $transactiontime-$appliedtimeclause, + Transactions.id + FROM Transactions + JOIN Tickets ON Transactions.ObjectId = Tickets.id + LEFT JOIN acct_rt_transaction + ON Transactions.id = acct_rt_transaction.transaction_id + $where + GROUP BY $groupby + ORDER BY Transactions.Created +"; + +my $count_query = "SELECT COUNT(*) FROM Transactions $where"; + +my $link = [ "${p}rt/Ticket/Display.html?id=", sub { shift->[0]; } ]; + +</%init> |