spacing
[freeside.git] / httemplate / browse / part_pkg.cgi
index 87aa792..1053a6a 100755 (executable)
                  'html_foot'             => $html_foot,
              )
 %>
+<%def .style>
+<STYLE TYPE="text/css">
+  .taxproduct_desc {
+    color: blue;
+    text-decoration: underline dotted;
+  }
+</STYLE>
+<SCRIPT TYPE="text/javascript">
+$().ready(function() {
+  $('.taxproduct_desc').tooltip({});
+});
+$(document).ready(function(){
+    $(this).scrollTop(0);
+});
+</SCRIPT>
+</%def>
 <%init>
 
 my $curuser = $FS::CurrentUser::CurrentUser;
@@ -45,7 +61,9 @@ die "access denied"
 
 my $conf = new FS::Conf;
 my $taxclasses = $conf->exists('enable_taxclasses');
+my $taxvendor = $conf->config('tax_data_vendor');
 my $money_char = $conf->config('money_char') || '$';
+my $disable_counts = $conf->exists('config-disable_counts') ? 1 : 0;
 
 my $select = '*';
 my $orderby = 'pkgpart';
@@ -76,6 +94,10 @@ if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
 }
 $cgi->delete('classnum');
 
+if ( $cgi->param('pkgpartbatch') =~ /^([\w\/\-\:\. ]+)$/ ) {
+  push @where, "pkgpartbatch = '$1' ";
+}
+
 if ( $cgi->param('missing_recur_fee') ) {
   push @where, "NOT EXISTS ( SELECT 1 FROM part_pkg_option
                                WHERE optionname = 'recur_fee'
@@ -84,6 +106,14 @@ if ( $cgi->param('missing_recur_fee') ) {
                            )";
 }
 
+if ( $cgi->param('ratenum') =~ /^(\d+)$/ ) {
+  push @where, "EXISTS( SELECT 1 FROM part_pkg_option
+                          WHERE optionname LIKE '%ratenum'
+                            AND optionvalue = '$1'
+                            AND part_pkg_option.pkgpart = part_pkg.pkgpart
+                      )";
+}
+
 if ( $cgi->param('family') =~ /^(\d+)$/ ) {
   $family_pkgpart = $1;
   push @where, "family_pkgpart = $1";
@@ -117,40 +147,43 @@ my $count_cust_pkg_cancel = "
       AND cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0
 ";
 
-$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
-      AND setup IS NOT NULL AND setup != 0
-  ) AS num_suspended,
-
-  ( $count_cust_pkg
-      AND ( cancel IS NULL OR cancel = 0 )
-      AND susp IS NOT NULL AND susp != 0
-      AND ( setup IS NULL OR setup = 0 )
-  ) AS num_on_hold,
-
-  ( $count_cust_pkg_cancel
-      AND (cust_pkg_next.pkgnum IS NULL
-           OR cust_pkg_next.pkgpart != cust_pkg.pkgpart)
-  ) AS num_cancelled
+unless ( $disable_counts ) {
+  $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
+        AND setup IS NOT NULL AND setup != 0
+    ) AS num_suspended,
+
+    ( $count_cust_pkg
+        AND ( cancel IS NULL OR cancel = 0 )
+        AND susp IS NOT NULL AND susp != 0
+        AND ( setup IS NULL OR setup = 0 )
+    ) AS num_on_hold,
+
+    ( $count_cust_pkg_cancel
+        AND (cust_pkg_next.pkgnum IS NULL
+            OR cust_pkg_next.pkgpart != cust_pkg.pkgpart)
+    ) AS num_cancelled
+
+  ";
+}
 
-";
 # About the num_cancelled expression: packages that were changed, but 
 # kept the same pkgpart, are considered "moved", not "canceled" (because
 # this is the part_pkg UI).  We could show the count of those but it's 
@@ -168,6 +201,7 @@ my $html_init = qq!
     </FORM>
     <BR><BR>
   !;
+$html_init .= include('.style');
 
 $cgi->param('dummy', 1);
 
@@ -243,6 +277,7 @@ push @fields, sub {
                   $part_pkg->part_pkg_discount;
 
   [
+    # Line 0: Family package link (if applicable)
     ( !$family_pkgpart &&
       $part_pkg->pkgpart == $part_pkg->family_pkgpart ? () : [
       {
@@ -253,13 +288,13 @@ push @fields, sub {
         'link' => $p.'browse/part_pkg.cgi?family='.$part_pkg->family_pkgpart,
       }
     ] ),
-    [
+    [ # Line 1: Plan type (Anniversary, Prorate, Call Rating, etc.)
       { data =>$plan,
         align=>'center',
         colspan=>2,
       },
     ],
-    [
+    [ # Line 2: Setup fee
       { data =>$money_char.
                sprintf('%.2f ', $part_pkg->option('setup_fee') ),
         align=>'right'
@@ -274,7 +309,7 @@ push @fields, sub {
         align=>'left',
       },
     ],
-    [
+    [ # Line 3: Recurring fee
       { data=>(
           $is_recur
             ? $money_char. sprintf('%.2f', $part_pkg->option('recur_fee'))
@@ -284,20 +319,56 @@ push @fields, sub {
         colspan=> ( $is_recur ? 1 : 2 ),
       },
       ( $is_recur
-        ?  { data => ( $is_recur
-               ? ' &nbsp; '. $part_pkg->freq_pretty.
-                 ( $part_pkg->option('recur_fee') == 0
-                     && $part_pkg->recur_show_zero
-                   ? ' (printed on invoices)'
-                   : ''
-                 )
-               : '' ),
+        ?  { data => ' &nbsp; '. $part_pkg->freq_pretty.
+                     ( $part_pkg->option('recur_fee') == 0
+                         && $part_pkg->recur_show_zero
+                       ? ' (printed on invoices)'
+                       : ''
+                     ),
              align=>'left',
            }
         : ()
       ),
     ],
-    (
+    [ { data => '&nbsp;' }, ], # Line 4: empty
+    ( $part_pkg->adjourn_months ? 
+      [ # Line 5: Adjourn months
+        { data => mt('After [quant,_1,month], <strong>suspend</strong> the package.',
+                     $part_pkg->adjourn_months),
+          align => 'left',
+          size  => -1,
+          colspan => 2,
+        }
+      ] : ()
+    ),
+    ( $part_pkg->contract_end_months ? 
+      [ # Line 6: Contract end months
+        { data => mt('After [quant,_1,month], <strong>contract ends</strong>.',
+                     $part_pkg->contract_end_months),
+          align => 'left',
+          size  => -1,
+          colspan => 2,
+        }
+      ] : ()
+    ),
+    ( $part_pkg->expire_months ? 
+      [ # Line 7: Expire months and automatic transfer
+        { data => $part_pkg->change_to_pkgpart ?
+                    mt('After [quant,_1,month], <strong>change to</strong> ',
+                      $part_pkg->expire_months) .
+                    qq(<a href="${p}edit/part_pkg.cgi?) .
+                      $part_pkg->change_to_pkgpart .
+                      qq(">) . $part_pkg->change_to_pkg->pkg . qq(</a>) . '.'
+                  : mt('After [quant,_1,month], <strong>cancel</strong> the package.',
+                     $part_pkg->expire_months)
+          ,
+          align => 'left',
+          size  => -1,
+          colspan => 2,
+        }
+      ] : ()
+    ),
+    ( # Usage prices
       map { my $amount = $_->amount / ($_->target_info->{multiplier} || 1);
             my $label = $_->target_info->{label};
             [
@@ -311,7 +382,8 @@ push @fields, sub {
           }
         $part_pkg->part_pkg_usageprice
     ),
-    ( map { my $dst_pkg = $_->dst_pkg;
+    ( # Supplementals
+      map { my $dst_pkg = $_->dst_pkg;
             [
               { data => 'Supplemental: &nbsp;'.
                         '<A HREF="#'. $dst_pkg->pkgpart . '">' .
@@ -323,7 +395,8 @@ push @fields, sub {
           }
       $part_pkg->supp_part_pkg_link
     ),
-    ( map { 
+    ( # Billing add-ons/bundle packages
+      map { 
             my $dst_pkg = $_->dst_pkg;
             [ 
               { data => 'Add-on:&nbsp;'.$dst_pkg->pkg_comment,
@@ -334,7 +407,8 @@ push @fields, sub {
           }
       $part_pkg->bill_part_pkg_link
     ),
-    ( scalar(@discounts)
+    ( # Discounts available
+      scalar(@discounts)
         ?  [ 
               { data => '<b>Discounts</b>',
                 align=>'center', #?
@@ -356,7 +430,7 @@ push @fields, sub {
           @discounts
         : ()
     ),
-  ];
+  ]; # end of "middle column"
 
 #  $plan_labels{$part_pkg->plan}.'<BR>'.
 #    $money_char.sprintf('%.2f setup<BR>', $part_pkg->option('setup_fee') ).
@@ -447,15 +521,7 @@ if ( $acl_edit_global ) {
 
 #if ( $cgi->param('active') ) {
   push @header, 'Customer<BR>packages';
-  my %col = (
-    'on hold'         => '7E0079', #purple!
-    'not yet billed'  => '009999', #teal? cyan?
-    'active'          => '00CC00',
-    'suspended'       => 'FF9900',
-    'cancelled'       => 'FF0000',
-    #'one-time charge' => '000000',
-    'charge'          => '000000',
-  );
+  my %col = %{ FS::cust_pkg->statuscolors };
   my $cust_pkg_link = $p. 'search/cust_pkg.cgi?pkgpart=';
   push @fields, sub { my $part_pkg = shift;
                         [
@@ -474,7 +540,7 @@ if ( $acl_edit_global ) {
                                 {
                                  'data'  => '<B><FONT COLOR="#'. $col{$label}. '">'.
                                             $part_pkg->get("num_$_").
-                                            '</FONT></B>',
+                                            '</FONT></B>&nbsp;',
                                  'align' => 'right',
                                 },
                                 {
@@ -485,7 +551,7 @@ if ( $acl_edit_global ) {
                                                   : ''
                                               ),
                                  'align' => 'left',
-                                 'link'  => ( $part_pkg->get("num_$_")
+                                 'link'  => ( $part_pkg->get("num_$_") || $disable_counts
                                                 ? $cust_pkg_link.
                                                   $part_pkg->pkgpart.
                                                   ";magic=$magic"
@@ -503,7 +569,7 @@ if ( $acl_edit_global ) {
                                 'action'      => "${p}edit/bulk-cust_pkg.html?".
                                                  'pkgpart='.$part_pkg->pkgpart,
                                 'actionlabel' => 'Change Packages',
-                                'width'       => 569,
+                                'width'       => 960,
                                 'height'      => 210,
                               ).' ]</FONT>',
                             'align' => 'left',
@@ -518,6 +584,49 @@ if ( $taxclasses ) {
   push @header, 'Taxclass';
   push @fields, sub { shift->taxclass() || '&nbsp;'; };
   $align .= 'l';
+} elsif ( $taxvendor ) {
+  push @header, 'Tax product';
+  my @classnums = ( 'setup', 'recur' );
+  my @classnames = ( 'Setup', 'Recur' );
+  foreach ( qsearch('usage_class', { disabled => '' }) ) {
+    push @classnums, $_->classnum;
+    push @classnames, $_->classname;
+  }
+  my $taxproduct_sub = sub {
+    my $ppt = shift;
+    '<SPAN CLASS="taxproduct_desc" TITLE="' .
+      encode_entities($ppt->description) .
+    '">' . encode_entities($ppt->taxproduct) . '</SPAN>'
+  };
+  my $taxproduct_list_sub = sub {
+    my $part_pkg = shift;
+    my $base_ppt = $part_pkg->taxproduct;
+    my $out = [];
+    if ( $base_ppt ) {
+      push @$out, [
+        { 'data'  => '', 'align' => 'left' },
+        { 'data'  => &$taxproduct_sub($base_ppt), 'align' => 'right' },
+      ];
+    }
+    if ( my $units_ppt = $part_pkg->units_taxproduct ) {
+      push @$out, [
+        { 'data'  => emt('Lines'), 'align' => 'left' },
+        { 'data'  => &$taxproduct_sub($units_ppt), 'align' => 'right' },
+      ];
+    }
+    for (my $i = 0; $i < scalar @classnums; $i++) {
+      my $num = $part_pkg->option('usage_taxproductnum_' . $classnums[$i]);
+      next if !$num;
+      my $ppt = FS::part_pkg_taxproduct->by_key($num);
+      push @$out, [
+        { 'data'  => $classnames[$i], 'align' => 'left', },
+        { 'data'  => &$taxproduct_sub($ppt), 'align' => 'right' },
+      ];
+    }
+    $out;
+  };
+  push @fields, $taxproduct_list_sub;
+  $align .= 'l';
 }
 
 # make a table of report class optionnames =>  the actual 
@@ -555,9 +664,12 @@ push @fields,
                                 },
                               ];
                             }
+                        sort
                         grep { $options{$_} =~ /\S/ } 
                         grep { $_ !~ /^(setup|recur)_fee$/ 
-                               and $_ !~ /^report_option_\d+$/ }
+                               and $_ !~ /^report_option_\d+$/
+                               and $_ !~ /^usage_taxproductnum_/
+                             }
                         keys %options
                       );
                       if ( @report_options ) {
@@ -615,7 +727,7 @@ push @fields,
 
                              [
                                {
-                                 'data'  => '<B>'. $pkg_svc->quantity. '</B>',
+                                 'data'  => '<B>'. $pkg_svc->quantity. '</B>&nbsp;',
                                  'align' => 'right'
                                },
                                {
@@ -691,8 +803,32 @@ if ( $acl_edit_bulk ) {
   $align .= 'c';
   $html_form = qq!<FORM ACTION="${p}edit/bulk-part_pkg.html" METHOD="POST">!;
   $html_foot = include('/search/elements/checkbox-foot.html',
-      submit  => 'edit report classes', # for now it's only report classes
-  ) . '</FORM>';
+                 actions => [
+                   { label  => 'edit packages',
+                     onclick=> include('/elements/popup_link_onclick.html',
+                                 'label'       => 'edit',
+                                 'js_action'   => qq{
+                                   '${p}edit/bulk-part_pkg.html?' + \$('input[name=pkgpart]').serialize()
+                                 },
+                                 'actionlabel' => 'Bulk edit packages',
+                                 'width'       => 960,
+                                 'height'      => 420,
+                               )
+                   },
+                   { label  => 'change customers packages',
+                     onclick=> include('/elements/popup_link_onclick.html',
+                                 'label'       => 'change',
+                                 'js_action'   => qq{
+                                   '${p}edit/bulk-cust_pkg.html?' + \$('input[name=pkgpart]').serialize()
+                                 },
+                                 'actionlabel' => 'Change customer packages',
+                                 'width'       => 960,
+                                 'height'      => 420,
+                               )
+                   },
+                 ],
+               ).
+               '</FORM>';
 }
 
 my @menubar;