X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=httemplate%2Fbrowse%2Fpart_pkg.cgi;h=8b50a509fd15a946727b991679b0a850e06f2fae;hb=3d0a1bb06b895c5be6e3f0517d355442a6b1e125;hp=e6454639f612e7a92066b90bb4b973a0a8e7f1f4;hpb=c1bb4ddb71147d0571bd301a6d8c452fdf0e1bc9;p=freeside.git
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index e6454639f..8b50a509f 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -1,169 +1,621 @@
-
-<%
-
-my %search;
-if ( $cgi->param('showdisabled') ) {
- %search = ();
-} else {
- %search = ( 'disabled' => '' );
-}
+<% include( 'elements/browse.html',
+ 'title' => 'Package Definitions',
+ 'menubar' => \@menubar,
+ 'html_init' => $html_init,
+ 'html_form' => $html_form,
+ 'html_posttotal' => $html_posttotal,
+ 'name' => 'package definitions',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 4,
+ 'agent_virt' => 1,
+ 'agent_null_right' => [ $edit, $edit_global ],
+ 'agent_null_right_link' => $edit_global,
+ 'agent_pos' => 6,
+ 'query' => { 'select' => $select,
+ 'table' => 'part_pkg',
+ 'hashref' => \%hash,
+ 'extra_sql' => $extra_sql,
+ 'order_by' => "ORDER BY $orderby"
+ },
+ 'count_query' => $count_query,
+ 'header' => \@header,
+ 'fields' => \@fields,
+ 'links' => \@links,
+ 'align' => $align,
+ 'link_field' => 'pkgpart',
+ 'html_init' => $html_init,
+ 'html_foot' => $html_foot,
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $edit = 'Edit package definitions';
+my $edit_global = 'Edit global package definitions';
+my $acl_edit = $curuser->access_right($edit);
+my $acl_edit_global = $curuser->access_right($edit_global);
+my $acl_config = $curuser->access_right('Configuration'); #to edit services
+ #and agent types
+ #and bulk change
+my $acl_edit_bulk = $curuser->access_right('Bulk edit package definitions');
+
+die "access denied"
+ unless $acl_edit || $acl_edit_global;
-my @part_pkg = qsearch('part_pkg', \%search );
-my $total = scalar(@part_pkg);
+my $conf = new FS::Conf;
+my $taxclasses = $conf->exists('enable_taxclasses');
+my $money_char = $conf->config('money_char') || '$';
+
+my $select = '*';
+my $orderby = 'pkgpart';
+my %hash = ();
+my $extra_count = '';
+my $family_pkgpart;
-my $sortby;
-my %num_active_cust_pkg = ();
-my( $suspended_sth, $canceled_sth ) = ( '', '' );
if ( $cgi->param('active') ) {
- my $active_sth = dbh->prepare(
- 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
- ' AND ( cancel IS NULL OR cancel = 0 )'.
- ' AND ( susp IS NULL OR susp = 0 )'
- ) or die dbh->errstr;
- foreach my $part_pkg ( @part_pkg ) {
- $active_sth->execute($part_pkg->pkgpart) or die $active_sth->errstr;
- $num_active_cust_pkg{$part_pkg->pkgpart} =
- $active_sth->fetchrow_arrayref->[0];
- }
- $sortby = sub {
- $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart};
- };
+ $orderby = 'num_active DESC';
+}
- $suspended_sth = dbh->prepare(
- 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
- ' AND ( cancel IS NULL OR cancel = 0 )'.
- ' AND susp IS NOT NULL AND susp != 0'
- ) or die dbh->errstr;
+my @where = ();
- $canceled_sth = dbh->prepare(
- 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
- ' AND cancel IS NOT NULL AND cancel != 0'
- ) or die dbh->errstr;
+#if ( $cgi->param('activeONLY') ) {
+# push @where, ' WHERE num_active > 0 '; #XXX doesn't affect count...
+#}
-} else {
- $sortby = sub { $a->pkgpart <=> $b->pkgpart; };
+if ( $cgi->param('recurring') ) {
+ $hash{'freq'} = { op=>'!=', value=>'0' };
+ $extra_count = " freq != '0' ";
}
-my $conf = new FS::Conf;
-my $taxclasses = $conf->exists('enable_taxclasses');
+my $classnum = '';
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ $classnum = $1;
+ push @where, $classnum ? "classnum = $classnum"
+ : "classnum IS NULL";
+}
+$cgi->delete('classnum');
-%>
-<%= include("/elements/header.html","Package Definition Listing",menubar( 'Main Menu' => $p )) %>
-<% unless ( $cgi->param('active') ) { %>
- One or more service definitions are grouped together into a package
- definition and given pricing information. Customers purchase packages
- rather than purchase services directly.
- Add a new package definition
-
-<% } %>
-
-<%= $total %> package definitions
-<% if ( $cgi->param('showdisabled') ) { $cgi->param('showdisabled', 0); %>
- ( hide disabled packages )
-<% } else { $cgi->param('showdisabled', 1); %>
- ( show disabled packages )
-<% } %>
-
-<% my $colspan = $cgi->param('showdisabled') ? 2 : 3; %>
-
-<%= &table() %>
-
- >Package |
- Comment |
-<% if ( $cgi->param('active') ) { %>
- Customer packages |
-<% } %>
- Freq. |
-<% if ( $taxclasses ) { %>
- Taxclass |
-<% } %>
- Plan |
- Data |
- Service |
- Quan. |
-<% if ( dbdef->table('pkg_svc')->column('primary_svc') ) { %>
- Primary |
-<% } %>
-
-
-
-<%
-foreach my $part_pkg ( sort $sortby @part_pkg ) {
- my @pkg_svc = $part_pkg->pkg_svc;
- my($rowspan)=scalar(@pkg_svc);
- my $plandata;
- if ( $part_pkg->plan ) {
- $plandata = $part_pkg->plandata;
- $plandata =~ s/^(\w+)=/$1 /mg;
- $plandata =~ s/\n/
/g;
- } else {
- $part_pkg->plan('(legacy)');
- $plandata = "Setup ". $part_pkg->setup.
- "
Recur ". $part_pkg->recur;
- }
-%>
-
- ><%= $part_pkg->pkgpart %> |
-
-<% unless ( $cgi->param('showdisabled') ) { %>
- >
- <% if ( $part_pkg->disabled ) { %>
- DISABLED
- <% } %>
- |
-<% } %>
-
- ><%= $part_pkg->pkg %> |
- ><%= $part_pkg->comment %> |
-
-<% if ( $cgi->param('active') ) { %>
- >
- <%= $num_active_cust_pkg{$part_pkg->pkgpart} %> active
-
- <% $suspended_sth->execute( $part_pkg->pkgpart )
- or die $suspended_sth->errstr;
- my $num_suspended = $suspended_sth->fetchrow_arrayref->[0];
- %>
- <%= $num_suspended %> suspended
-
- <% $canceled_sth->execute( $part_pkg->pkgpart )
- or die $canceled_sth->errstr;
- my $num_canceled = $canceled_sth->fetchrow_arrayref->[0];
- %>
- <%= $num_canceled %> canceled
- |
-<% } %>
-
- ><%= $part_pkg->freq_pretty %> |
-
-<% if ( $taxclasses ) { %>
- ><%= $part_pkg->taxclass || ' ' %> |
-<% } %>
-
- ><%= $part_pkg->plan %> |
- ><%= $plandata %> |
-
-<%
- my($n)="";
- foreach my $pkg_svc ( @pkg_svc ) {
- my($svcpart)=$pkg_svc->getfield('svcpart');
- my($part_svc) = qsearchs('part_svc',{'svcpart'=> $svcpart });
- print $n,qq!!,
- $part_svc->getfield('svc')," | ",
- $pkg_svc->getfield('quantity')," | ";
- if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
- print '';
- print 'PRIMARY' if $pkg_svc->primary_svc =~ /^Y/i;
- print ' | ';
- }
- print "
\n";
- $n="";
+if ( $cgi->param('missing_recur_fee') ) {
+ push @where, "0 = ( SELECT COUNT(*) FROM part_pkg_option
+ WHERE optionname = 'recur_fee'
+ AND part_pkg_option.pkgpart = part_pkg.pkgpart
+ AND CAST( optionvalue AS NUMERIC ) > 0
+ )";
+}
+
+if ( $cgi->param('family') =~ /^(\d+)$/ ) {
+ $family_pkgpart = $1;
+ push @where, "family_pkgpart = $1";
+ # Hiding disabled or one-time charges and limiting by classnum aren't
+ # very useful in this mode, so all links should still refer back to the
+ # non-family-limited display.
+ $cgi->param('showdisabled', 1);
+ $cgi->delete('family');
+}
+
+push @where, FS::part_pkg->curuser_pkgs_sql
+ unless $acl_edit_global;
+
+my $extra_sql = scalar(@where)
+ ? ( scalar(keys %hash) ? ' AND ' : ' WHERE ' ).
+ join( 'AND ', @where)
+ : '';
+
+my $agentnums_sql = $curuser->agentnums_sql( 'table'=>'cust_main' );
+my $count_cust_pkg = "
+ SELECT COUNT(*) FROM cust_pkg LEFT JOIN cust_main USING ( custnum )
+ WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+ AND $agentnums_sql
+";
+
+$select = "
+
+ *,
+
+ ( $count_cust_pkg
+ AND ( setup IS NULL OR setup = 0 )
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND ( susp IS NULL OR susp = 0 )
+ ) AS num_not_yet_billed,
+
+ ( $count_cust_pkg
+ AND setup IS NOT NULL AND setup != 0
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND ( susp IS NULL OR susp = 0 )
+ ) AS num_active,
+
+ ( $count_cust_pkg
+ AND ( cancel IS NULL OR cancel = 0 )
+ AND susp IS NOT NULL AND susp != 0
+ ) AS num_suspended,
+
+ ( $count_cust_pkg
+ AND cancel IS NOT NULL AND cancel != 0
+ ) AS num_cancelled
+
+";
+
+my $html_init = qq!
+ One or more service definitions are grouped together into a package
+ definition and given pricing information. Customers purchase packages
+ rather than purchase services directly.
+
+
+ !;
+
+$cgi->param('dummy', 1);
+
+my $filter_change =
+ qq(\n\n";
+
+#restore this so pagination works
+$cgi->param('classnum', $classnum) if length($classnum);
+
+#should hide this if there aren't any classes
+my $html_posttotal =
+ "$filter_change\n
( show class: ".
+ include('/elements/select-pkg_class.html',
+ #'curr_value' => $classnum,
+ 'value' => $classnum, #insist on 0 :/
+ 'onchange' => 'filter_change()',
+ 'pre_options' => [ '-1' => 'all',
+ '0' => '(none)', ],
+ 'disable_empty' => 1,
+ ).
+ ' )';
+
+my $recur_toggle = $cgi->param('recurring') ? 'show' : 'hide';
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 );
+
+$html_posttotal .=
+ '( '. "$recur_toggle one-time charges )";
+
+$cgi->param('recurring', $cgi->param('recurring') ^ 1 ); #put it back
+
+# ------
+
+my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ];
+
+my @header = ( '#', 'Package', 'Comment', 'Custom' );
+my @fields = ( 'pkgpart', 'pkg', 'comment',
+ sub{ ''.$_[0]->custom.'' }
+ );
+my $align = 'rllc';
+my @links = ( $link, $link, '', '' );
+
+unless ( 0 ) { #already showing only one class or something?
+ push @header, 'Class';
+ push @fields, sub { shift->classname || '(none)'; };
+ $align .= 'l';
+}
+
+if ( $conf->exists('pkg-addon_classnum') ) {
+ push @header, "Add'l order class";
+ push @fields, sub { shift->addon_classname || '(none)'; };
+ $align .= 'l';
+}
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+
+tie my %plan_labels, 'Tie::IxHash',
+ map { $_ => ( $plans{$_}->{'shortname'} || $plans{$_}->{'name'} ) }
+ keys %plans;
+
+push @header, 'Pricing';
+$align .= 'r'; #?
+push @fields, sub {
+ my $part_pkg = shift;
+ (my $plan = $plan_labels{$part_pkg->plan} ) =~ s/ / /g;
+ my $is_recur = ( $part_pkg->freq ne '0' );
+ my @discounts = sort { $a->months <=> $b->months }
+ map { $_->discount }
+ $part_pkg->part_pkg_discount;
+
+ [
+ ( !$family_pkgpart &&
+ $part_pkg->pkgpart == $part_pkg->family_pkgpart ? () : [
+ {
+ 'align'=> 'center',
+ 'colspan' => 2,
+ 'size' => '-1',
+ 'data' => 'Show all versions',
+ 'link' => $p.'browse/part_pkg.cgi?family='.$part_pkg->family_pkgpart,
+ }
+ ] ),
+ [
+ { data =>$plan,
+ align=>'center',
+ colspan=>2,
+ },
+ ],
+ [
+ { data =>$money_char.
+ sprintf('%.2f ', $part_pkg->option('setup_fee') ),
+ align=>'right'
+ },
+ { data => ( ( $is_recur ? ' setup' : ' one-time' ).
+ ( $part_pkg->option('recur_fee') == 0
+ && $part_pkg->setup_show_zero
+ ? ' (printed on invoices)'
+ : ''
+ )
+ ),
+ align=>'left',
+ },
+ ],
+ [
+ { data=>(
+ $is_recur
+ ? $money_char. sprintf('%.2f', $part_pkg->option('recur_fee'))
+ : $part_pkg->freq_pretty
+ ),
+ align=> ( $is_recur ? 'right' : 'center' ),
+ colspan=> ( $is_recur ? 1 : 2 ),
+ },
+ ( $is_recur
+ ? { data => ( $is_recur
+ ? ' '. $part_pkg->freq_pretty.
+ ( $part_pkg->option('recur_fee') == 0
+ && $part_pkg->recur_show_zero
+ ? ' (printed on invoices)'
+ : ''
+ )
+ : '' ),
+ align=>'left',
+ }
+ : ()
+ ),
+ ],
+ ( map { my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Supplemental: '.
+ '' .
+ $dst_pkg->pkg . '',
+ align=> 'center',
+ colspan => 2,
+ }
+ ]
+ }
+ $part_pkg->supp_part_pkg_link
+ ),
+ ( map {
+ my $dst_pkg = $_->dst_pkg;
+ [
+ { data => 'Add-on: '.$dst_pkg->pkg_comment,
+ align=>'center', #?
+ colspan=>2,
+ }
+ ]
+ }
+ $part_pkg->bill_part_pkg_link
+ ),
+ ( scalar(@discounts)
+ ? [
+ { data => 'Discounts',
+ align=>'center', #?
+ colspan=>2,
+ }
+ ]
+ : ()
+ ),
+ ( scalar(@discounts)
+ ? map {
+ [
+ { data => $_->months. ':',
+ align => 'right',
+ },
+ { data => $_->amount ? '$'. $_->amount : $_->percent. '%'
+ }
+ ]
+ }
+ @discounts
+ : ()
+ ),
+ ];
+
+# $plan_labels{$part_pkg->plan}.'
'.
+# $money_char.sprintf('%.2f setup
', $part_pkg->option('setup_fee') ).
+# ( $part_pkg->freq ne '0'
+# ? $money_char.sprintf('%.2f ', $part_pkg->option('recur_fee') )
+# : ''
+# ).
+# $part_pkg->freq_pretty; #.'
'
+};
+
+###
+# Agent goes here if displayed
+###
+
+#agent type
+if ( $acl_edit_global ) {
+ #really we just want a count, but this is fine unless someone has tons
+ my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+ if ( scalar(@all_agent_types) > 1 ) {
+ push @header, 'Agent types';
+ my $typelink = $p. 'edit/agent_type.cgi?';
+ push @fields, sub { my $part_pkg = shift;
+ [
+ map { my $agent_type = $_->agent_type;
+ [
+ { 'data' => $agent_type->atype, #escape?
+ 'align' => 'left',
+ 'link' => ( $acl_config
+ ? $typelink.
+ $agent_type->typenum
+ : ''
+ ),
+ },
+ ];
+ }
+ $part_pkg->type_pkgs
+ ];
+ };
+ $align .= 'l';
}
-%>
+}
+
+#if ( $cgi->param('active') ) {
+ push @header, 'Customer
packages';
+ my %col = (
+ 'not yet billed' => '009999', #teal? cyan?
+ 'active' => '00CC00',
+ 'suspended' => 'FF9900',
+ 'cancelled' => 'FF0000',
+ #'one-time charge' => '000000',
+ 'charge' => '000000',
+ );
+ my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart=';
+ push @fields, sub { my $part_pkg = shift;
+ [
+ map( {
+ my $magic = $_;
+ my $label = $_;
+ if ( $magic eq 'active' && $part_pkg->freq == 0 ) {
+ $magic = 'inactive';
+ #$label = 'one-time charge',
+ $label = 'charge',
+ }
+ $label= 'not yet billed' if $magic eq 'not_yet_billed';
+
+ [
+ {
+ 'data' => ''.
+ $part_pkg->get("num_$_").
+ '',
+ 'align' => 'right',
+ },
+ {
+ 'data' => $label.
+ ( $part_pkg->get("num_$_") != 1
+ && $label =~ /charge$/
+ ? 's'
+ : ''
+ ),
+ 'align' => 'left',
+ 'link' => ( $part_pkg->get("num_$_")
+ ? $cust_pkg_link.
+ $part_pkg->pkgpart.
+ ";magic=$magic"
+ : ''
+ ),
+ },
+ ],
+ } (qw( not_yet_billed active suspended cancelled ))
+ ),
+ ($acl_config ?
+ [ {},
+ { 'data' => '[ '.
+ include('/elements/popup_link.html',
+ 'label' => 'change',
+ 'action' => "${p}edit/bulk-cust_pkg.html?".
+ 'pkgpart='.$part_pkg->pkgpart,
+ 'actionlabel' => 'Change Packages',
+ 'width' => 569,
+ 'height' => 210,
+ ).' ]',
+ 'align' => 'left',
+ }
+ ] : () ),
+ ];
+ };
+ $align .= 'r';
+#}
+
+if ( $taxclasses ) {
+ push @header, 'Taxclass';
+ push @fields, sub { shift->taxclass() || ' '; };
+ $align .= 'l';
+}
+
+# make a table of report class optionnames => the actual
+my %report_optionname_name = map { 'report_option_'.$_->num, $_->name }
+ qsearch('part_pkg_report_option', { disabled => '' });
-
-<% } %>
+push @header, 'Plan options',
+ 'Services';
+ #'Service', 'Quan', 'Primary';
-
-