customer package browse: reset page on option change, RT#39822
[freeside.git] / httemplate / view / cust_main / packages.html
index 068a827..43ebb28 100755 (executable)
-<%
-  my( $cust_main ) = @_;
-  my $conf = new FS::Conf;
-
-  my $packages = get_packages($cust_main, $conf);
-%>
-
-<A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A>
-
-<%= include('order_pkg.html', $cust_main ) %>
-
-<% if ( $conf->config('payby-default') ne 'HIDE' ) { %>
-  <%= include('quick-charge.html', $cust_main ) %>
-<% } %>
-
-<A HREF="<%= $p %>edit/cust_pkg.cgi?<%= $cust_main->custnum %>">Bulk order and cancel packages</A> (preserves services)
-<BR><BR>
-
-<% if ( @$packages ) { %>
-Current packages
-<% } %>
-
-<% if ( $cust_main->num_cancelled_pkgs ) {
-     if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
-          || ( $conf->exists('hidecancelledpackages')
-               && ! $cgi->param('showcancelledpackages')
-             )
-        )
-     {
-       $cgi->param('showcancelledpackages', 1);
-%>
-  ( <a href="<%= $cgi->self_url %>">show
-<%   } else {
-       $cgi->param('showcancelledpackages', 0);
-%>
-  ( <a href="<%= $cgi->self_url %>">hide
-<%   } %>
- cancelled packages</a> )
-<% } %>
-
-<% if ( @$packages ) { %>
-
-<TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
-<TR>
-  <TH>Package</TH>
-  <TH>Status</TH>
-  <TH COLSPAN=2>Services</TH>
-</TR>
-
-<%
-foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
-  my $rowspan = 0;
-
-  if ($pkg->{cancel}) {
-    $rowspan = 0;
-  } else {
-    foreach my $svcpart (@{$pkg->{svcparts}}) {
-      $rowspan += $svcpart->{count};
-      $rowspan++ if ($svcpart->{count} < $svcpart->{quantity});
-    }
-  } 
-%>
-
-<!--pkgnum: <%=$pkg->{pkgnum}%>-->
-<TR>
-  <TD ROWSPAN=<%=$rowspan%>>
-    <A NAME="cust_pkg<%=$pkg->{pkgnum}%>"><%=$pkg->{pkgnum}%></A>:
-    <%=$pkg->{pkg}%> - <%=$pkg->{comment}%><BR>
-<% unless ($pkg->{cancel}) { %>
-    (&nbsp;<%=pkg_change_link($pkg)%>&nbsp;)
-    (&nbsp;<%=pkg_dates_link($pkg)%>&nbsp;|&nbsp;<%=pkg_customize_link($pkg,$cust_main->custnum)%>&nbsp;)
-<% } %>
-  </TD>
-<%
-  #foreach (qw(setup last_bill next_bill susp expire cancel)) {
-  #  print qq!  <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_,$conf) . qq!</TD>\n!;
-  #}
-  print "<TD ROWSPAN=$rowspan>". &itable('');
-
-  sub myfreq {
-    my $part_pkg = shift;
-    my $freq = $part_pkg->freq_pretty;
-    $freq =~ s/ /&nbsp;/g;
-    $freq;
-  }
-
-  if ( $pkg->{cancel} ) { #status: cancelled
-
-    print '<TR><TD><FONT COLOR="#ff0000"><B>Cancelled&nbsp;</B></FONT></TD>'.
-          '<TD>'. pkg_datestr($pkg,'cancel',$conf). '</TD></TR>';
-    unless ( $pkg->{setup} ) {
-      print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
-    } else {
-      print "<TR><TD>Setup&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
-      print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
-        if $pkg->{'last_bill'};
-      print "<TR><TD>Suspended&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'susp',$conf). '</TD></TR>'
-        if $pkg->{'susp'};
-    }
-
-  } else {
+<STYLE TYPE="text/css">
+td.package {
+  vertical-align: top;
+  border-width: 0;
+  border-style: solid;
+}
+table.package {
+  border: none;
+  padding: 0;
+  border-spacing: 0;
+  width: 100%;
+}
+table.usage {
+  border: 1px solid black;
+  margin: auto;
+  width: 60%;
+  border-spacing: 0px;
+}
+.shared > * {
+  background-color: #ffffaa;
+}
+.row0 { background-color: #eeeeee; }
+.row1 { background-color: #ffffff; }
+
+table.hiddenrows {
+  width: 80%;
+  margin-left: 100px;
+  border: 1px solid #7E0079;
+  background-color: #cccccc;
+}
 
-    if ( $pkg->{susp} ) { #status: suspended
-      print '<TR><TD><FONT COLOR="#FF9900"><B>Suspended</B>&nbsp;</FONT></TD>'.
-            '<TD>'. pkg_datestr($pkg,'susp',$conf). '</TD></TR>';
-      unless ( $pkg->{setup} ) {
-        print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
-      } else {
-        print "<TR><TD>Setup&nbsp;</TD><TD>". 
-              pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
-      }
-      print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
-        if $pkg->{'last_bill'};
-      # next bill ??
-      print "<TR><TD>Expires&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
-        if $pkg->{'expire'};
-      print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_unsuspend_link($pkg).
-            '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
-
-    } else { #status: active
-
-      unless ( $pkg->{setup} ) { #not setup
-
-        print '<TR><TD COLSPAN=2>Not&nbsp;yet&nbsp;billed&nbsp;(';
-        unless ( $pkg->{freq} ) {
-          print 'one-time&nbsp;charge)</TD></TR>';
-          print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_cancel_link($pkg).
-                '&nbsp;)</TD</TR>';
-        } else {
-          print 'billed&nbsp;'. myfreq($pkg->{part_pkg}). ')</TD></TR>';
-        }
-
-      } else { #setup
-
-        unless ( $pkg->{freq} ) {
-          print "<TR><TD COLSPAN=2>One-time&nbsp;charge</TD></TR>".
-                '<TR><TD>Billed&nbsp;</TD><TD>'.
-                pkg_datestr($pkg,'setup',$conf). '</TD></TR>';
-        } else {
-          print '<TR><TD COLSPAN=2><FONT COLOR="#00CC00"><B>Active</B></FONT>'.
-                ',&nbsp;billed&nbsp;'. myfreq($pkg->{part_pkg}). '</TD></TR>'.
-                '<TR><TD>Setup&nbsp;</TD><TD>'.
-                pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
-        }
-
-      }
-
-      print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
-        if $pkg->{'last_bill'};
-      print "<TR><TD>Next&nbsp;bill&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'next_bill',$conf). '</TD></TR>'
-        if $pkg->{'next_bill'};
-      print "<TR><TD>Expires&nbsp;</TD><TD>".
-            pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
-        if $pkg->{'expire'};
-      if ( $pkg->{freq} ) {
-        print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_suspend_link($pkg).
-              '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
-      }
+.hiddenrows td {
+  text-align: center;
+}
 
-    }
+.rolldown_button {
+  min-width: 80px;
+  margin-left: 100px;
+  min-height: 20px;
+  background-color: #efefef;
+  border: 1px solid #7e0079;
+  z-index: 1;
+  text-align: center;
+}
 
+</STYLE>
+
+% # activate rolldown buttons for hidden package blocks
+<SCRIPT TYPE="text/javascript">
+function toggle_rolldown() {
+  var up_arrow = <% decode_entities('&#x2b06') |js_string %>;
+  var dn_arrow = <% decode_entities('&#x2b07') |js_string %>;
+  var pkgnum = this.id.replace('rolldown_', '');
+  var hidden = document.getElementById('cust_pkg'+pkgnum+'_block');
+  if (hidden.style.display == 'none') {
+    hidden.style.display = '';
+    this.textContent = this.textContent.replace(dn_arrow, up_arrow);
+  } else {
+    hidden.style.display = 'none';
+    this.textContent = this.textContent.replace(up_arrow, dn_arrow);
   }
-
-  print "</TABLE></TD>\n";
-
-  if ($rowspan == 0) { print qq!</TR>\n!; next; }
-
-  my $cnt = 0;
-  foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
-    foreach my $service (@{$svcpart->{services}}) {
-      print '<TR>' if ($cnt > 0);
-%>
-  <TD><%=svc_link($svcpart,$service)%></TD>
-  <TD><%=svc_label_link($svcpart,$service)%><BR>(&nbsp;<%=svc_unprovision_link($service)%>&nbsp;)</TD>
-</TR>
-<%
-      $cnt++;
-    }
-    if ($svcpart->{count} < $svcpart->{quantity}) {
-      print qq!<TR>\n! if ($cnt > 0);
-      print qq!  <TD COLSPAN=2>!.svc_provision_link($pkg, $svcpart, $conf).qq!</TD>\n</TR>\n!;
-    }
+}
+<&| /elements/onload.js &>
+var el;
+% if ( $cgi->param('fragment') =~ /^cust_pkg(\d+)$/ ) {
+% # IE-specific hack, but also unhide the row if it's in a hidden block
+el = document.getElementById('cust_pkg<% $1 %>');
+% }
+var all_buttons = document.getElementsByClassName('rolldown_button');
+for (var i = 0; i < all_buttons.length; i++) {
+  all_buttons[i].onclick = toggle_rolldown;
+  var block_id = all_buttons[i].id.replace('rolldown_', '');
+  if ( el && document.getElementById('cust_pkg'+block_id+'_block')
+                     .contains(el)
+     ) {
+    // then toggle it now
+    all_buttons[i].click();
   }
 }
-#end display packages
-%>
+if ( el ) el.scrollIntoView(true);
+</&>
+</SCRIPT>
+
+<TABLE>
+  <TR>
+    <TD ALIGN="left" VALIGN="top">
+
+% my $br = 0;
+
+% if ( $total_pkgs > $maxrecords ) {
+
+%   if ( 1 ) { #FS::pkg_class->num_pkg_class ) {
+      <% $br++ ? ' | ' : '' %>
+      Class: <& /elements/select-cust-pkg_class.html,
+                  curr_value  => scalar($cgi->param('classnum')),
+                  onchange    => 'classnum_changed(this);',
+                  pre_options => [ '-1' => 'all',
+                                    '0' => '(none)',
+                                 ],
+             &>
+%   }
+
+    <% $br++ ? ' | ' : '' %>
+    Status: <& /elements/select-cust_pkg-status.html,
+                 curr_value => scalar($cgi->param('status')),
+                 onchange   => 'status_changed(this);',
+            &>
+
+%   if ( 1 ) { #$cust_main->num_cust_location ) {
+      <% $br++ ? ' | ' : '' %>
+      Location: <& /elements/select-cust_location.html,
+                     cust_main   => $cust_main,
+                     curr_value  => scalar($cgi->param('locationnum')),
+                     addnew      => 0,
+                     onchange    => 'locationnum_changed(this);',
+                     pre_options => [ '-1' => 'all', ],
+                &>
+%   }
+
+  <SCRIPT TYPE="text/javascript">
+
+    function classnum_changed(what) {
+%     my $classnum = $cgi->param('classnum');
+%     $cgi->delete('classnum');
+      window.location = '<% $cgi->self_url %>;classnum=' + what.options[what.selectedIndex].value;
+%     $cgi->param('classnum', $classnum);
+    }
 
-</TABLE>
-<% } else { %>
-<BR>
-<% } %>
-
-<%
-#subroutines
-
-sub get_packages {
-  my $cust_main = shift or return undef;
-  my $conf = shift;
-  
-  my @packages = ();
-  my $method;
-  if (  $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
-     || ( $conf->exists('hidecancelledpackages')
-           && ! $cgi->param('showcancelledpackages') )
-     )
-  {
-    $method = 'ncancelled_pkgs';
-  } else {
-    $method = 'all_pkgs';
-  }
-  
-  foreach my $cust_pkg ( $cust_main->$method() ) {
-  
-    my $part_pkg = $cust_pkg->part_pkg;
-
-    my %pkg = ();
-
-    #to get back to the original object... should use it in the first place!!
-    $pkg{cust_pkg} = $cust_pkg;
-    $pkg{part_pkg} = $part_pkg;
-
-    $pkg{pkgnum} = $cust_pkg->pkgnum;
-    $pkg{pkg} = $part_pkg->pkg;
-    $pkg{pkgpart} = $part_pkg->pkgpart;
-    $pkg{comment} = $part_pkg->getfield('comment');
-    $pkg{freq} = $part_pkg->freq;
-    $pkg{setup} = $cust_pkg->getfield('setup');
-    $pkg{last_bill} = $cust_pkg->getfield('last_bill');
-    $pkg{next_bill} = $cust_pkg->getfield('bill');
-    $pkg{susp} = $cust_pkg->getfield('susp');
-    $pkg{expire} = $cust_pkg->getfield('expire');
-    $pkg{cancel} = $cust_pkg->getfield('cancel');
-
-  
-    my %svcparts = map {
-      $_->svcpart => {
-                       $_->part_svc->hash,
-                       'quantity' => $_->quantity,
-                       'count'    => $cust_pkg->num_cust_svc($_->svcpart),
-                       #'services' => [],
-                     };
-    } $part_pkg->pkg_svc;
-
-    foreach my $cust_svc ( $cust_pkg->cust_svc ) {
-      #warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n";
-      my $svc = {
-        'svcnum' => $cust_svc->svcnum,
-        'label'  => ($cust_svc->label)[1],
-      };
-
-      #false laziness with above, to catch extraneous services.  whole
-      #damn thing should be OO...
-      my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= {
-        $cust_svc->part_svc->hash,
-        'quantity' => 0,
-        'count'    => $cust_pkg->num_cust_svc($cust_svc->svcpart),
-        #'services' => [],
-      } );
-
-      push @{$svcpart->{services}}, $svc;
+    function status_changed(what) {
+%     my $status = $cgi->param('status');
+%     $cgi->delete('status');
+      window.location = '<% $cgi->self_url %>;status=' + what.options[what.selectedIndex].value;
+%     $cgi->param('status', $status);
+    }
 
+    function locationnum_changed(what) {
+%     my $locationnum = $cgi->param('locationnum');
+%     $cgi->delete('locationnum');
+      window.location = '<% $cgi->self_url %>;locationnum=' + what.options[what.selectedIndex].value;
+%     $cgi->param('locationnum', $locationnum);
     }
 
-    $pkg{svcparts} = [ values %svcparts ];
+  </SCRIPT>
+
+% }
+
+<% $br++ ? ' | ' : '' %>
+% if ( $cust_main->num_cancelled_pkgs ) {
+%     if ( $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+%          || ( $conf->exists('hidecancelledpackages')
+%               && ! $cgi->param('showcancelledpackages')
+%             )
+%        )
+%     {
+%       my $prev = $cgi->param('showcancelledpackages');
+%       $cgi->param('showcancelledpackages', 1);
+  <a href="<% $cgi->self_url %>"><% mt('show') |h %> 
+%       $cgi->param('showcancelledpackages', $prev);
+%   } else {
+%       $cgi->param('showcancelledpackages', 0);
+  <a href="<% $cgi->self_url %>"><% mt('hide') |h %> 
+%       $cgi->param('showcancelledpackages', 1);
+%   } 
+
+ <% mt('cancelled packages') |h %></a>
+% } 
+
+<% $br++ ? ' | ' : '' %>
+% if ( $cgi->param('showoldpackages') ) {
+%   $cgi->param('showoldpackages', 0);
+    <a href="<% $cgi->self_url %>"><% mt('hide old packages') |h %></a>
+%   $cgi->param('showoldpackages', 1);
+% } else {
+%   $cgi->param('showoldpackages', 1);
+    <a href="<% $cgi->self_url %>"><% mt('show old packages') |h %></a>
+%   $cgi->param('showoldpackages', 0);
+% }
+
+    </TD>
+
+  </TR>
+
+% my $pager = include('/elements/pager.html',
+%                       offset     => $offset,
+%                       num_rows   => scalar(@packages),
+%                       total      => $num_pkgs,
+%                       maxrecords => $maxrecords,
+%             );
+% if ( $num_pkgs > $maxrecords ) {
+  <TR>
+    <TD COLSPAN=2>
+      <% $pager %>
+    </TD>
+  </TR>
+% }
+
+  <TR>
+    <TD COLSPAN=2>
+
+%     $opt{cust_main} = $cust_main;
+%     $opt{packages}  = \@packages;
+%     $opt{cust_location_cache} = {};
+%     if ( $conf->exists('cust_pkg-group_by_location') ) {
+        <& locations.html, %opt &>
+%     } else { # in this format, put all packages in one section
+        <& /elements/table-grid.html &>
+        <& packages/section.html, %opt &>
+        </TABLE>
+%     }
+
+    </TD>
+  </TR>
+
+% if ( $num_pkgs > $maxrecords ) {
+  <TR>
+    <TD COLSPAN=2>
+      <% $pager %>
+    </TD>
+  </TR>
+% }
 
-    push @packages, \%pkg;
-  
-  }
-  
-  return \@packages;
+</TABLE>
+<%init>
 
-}
+my $cust_main = shift;
+my %opt = @_;
+my $conf = new FS::Conf;
 
-sub svc_link {
+my $curuser = $FS::CurrentUser::CurrentUser;
 
-  my ($svcpart, $svc) = (shift,shift) or return '';
-  return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!;
+my $countrydefault = scalar($conf->config('countrydefault')) || 'US';
 
+my $hide_cancelled = 0;
+if (  $cgi->param('showcancelledpackages') eq '0' #see if it was set by me
+   || ( $conf->exists('hidecancelledpackages')
+         && ! $cgi->param('showcancelledpackages') )
+   )
+{
+  $hide_cancelled = 1;
 }
 
-sub svc_label_link {
-
-  my ($svcpart, $svc) = (shift,shift) or return '';
-  return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!;
-
+my $cust_pkg_fields =
+  join(', ', map { "cust_pkg.$_ AS $_"          } fields('cust_pkg') );
+
+my $part_pkg_fields =
+  join(', ', ( map { "part_pkg.$_ AS part_pkg_$_" } fields('part_pkg') ),
+             'setup_option.optionvalue AS part_pkg__setup_fee',
+             'recur_option.optionvalue AS part_pkg__recur_fee',
+      );
+
+my $group_by =
+  join(', ', map "cust_pkg.$_", fields('cust_pkg') ). ', '.
+  join(', ', map "part_pkg.$_", fields('part_pkg') );
+
+my $num_svcs = '( SELECT COUNT(*) FROM cust_svc '.
+               '    WHERE cust_svc.pkgnum = cust_pkg.pkgnum ) AS num_svcs';
+
+my $addl_from = '
+  LEFT JOIN part_pkg USING ( pkgpart )
+  LEFT JOIN cust_pkg AS chgto   ON ( chgto.change_to_pkgnum = cust_pkg.pkgnum )
+  LEFT JOIN cust_pkg AS chgfrom ON ( chgfrom.change_pkgnum  = cust_pkg.pkgnum )
+';
+
+my $extra_sql =
+  ' AND cust_pkg.main_pkgnum IS NULL '. # supplemental package of something else
+  ' AND chgto.pkgnum IS NULL '. # ordered, not-yet-active change target
+  ' AND chgfrom.pkgnum IS NULL ';    # canceled package changed into another
+
+unless ( $cgi->param('showoldpackages') ) {
+  my $years = $conf->config('cust_main-packages-years') || 2;
+  my $then = time - $years * 31556926; #60*60*24*365.2422 is close enough
+
+  $extra_sql .= " AND (
+       ( part_pkg.freq  =  '0'
+           AND ( cust_pkg.setup  IS NULL OR cust_pkg.setup  > $then )
+       )
+    OR ( part_pkg.freq != '0'
+          AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel > $then )
+       )
+    OR EXISTS ( SELECT 1 FROM cust_svc WHERE cust_svc.pkgnum = cust_pkg.pkgnum )
+  )";
 }
 
-sub svc_provision_link {
-  my ($pkg, $svcpart, $conf) = @_;
-  ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/&nbsp;/g;
-  my $num_left = $svcpart->{quantity} - $svcpart->{count};
-  my $pkgnum_svcpart = "pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}";
-
-  my $url;
-  if ( $svcpart->{svcdb} eq 'svc_external'
-       && $conf->exists('svc_external-skip_manual')
-  ) {
-    $url = "${p}edit/process/$svcpart->{svcdb}.cgi?".
-           "pkgnum=$pkg->{pkgnum}&".
-           "svcpart=$svcpart->{svcpart}";
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+  my $classnum = $1;
+  if ( $classnum == 0 ) {
+    $extra_sql .= " AND part_pkg.classnum IS NULL ";
   } else {
-    $url = "${p}edit/$svcpart->{svcdb}.cgi?$pkgnum_svcpart";
+    $extra_sql .= " AND part_pkg.classnum = $classnum ";
   }
+}
 
-  my $link = qq!<A CLASS="provision" HREF="$url">!.
-             "Provision&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
-  if ( $conf->exists('legacy_link') ) {
-    $link .= '<BR>'.
-             qq!<A CLASS="provision" HREF="${p}misc/link.cgi?!.
-             qq!$pkgnum_svcpart">!.
-            "Link&nbsp;to&nbsp;legacy&nbsp;$svc_nbsp&nbsp;($num_left)</A>";
-  }
-  $link;
+if ( $cgi->param('status') =~ /^([\w ]+)$/ ) {
+  my $status = $1;
+  $extra_sql .= ' AND '. FS::cust_pkg->status_sql. " = '$status' ";
 }
 
-sub svc_unprovision_link {
-  my $svc = shift or return '';
-  qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}',!.
-  qq!'Permanently unprovision and delete this service?')">Unprovision</A>!;
+if ( $cgi->param('locationnum') =~ /^(\d+)$/ ) {
+  my $locationnum = $1;
+  $extra_sql .= " AND cust_pkg.locationnum = $locationnum ";
 }
 
-# This should be generalized to use config options to determine order.
-sub pkgsort_pkgnum_cancel {
-  if ($a->{cancel} and $b->{cancel}) {
-    return ($a->{pkgnum} <=> $b->{pkgnum});
-  } elsif ($a->{cancel} or $b->{cancel}) {
-    return (-1) if ($b->{cancel});
-    return (1) if ($a->{cancel});
-    return (0);
-  } else {
-    return($a->{pkgnum} <=> $b->{pkgnum});
+my $total_pkgs = $cust_main->all_pkgs;
+
+my $method = $hide_cancelled ? 'ncancelled_pkgs' : 'all_pkgs';
+my $num_pkgs = $cust_main->$method({ 
+  'addl_from' => $addl_from,
+  'extra_sql' => $extra_sql,
+});
+
+my $maxrecords = 10;
+my $offset = $cgi->param('offset') =~ /^(\d+)$/ ? $1 : 0;
+$cgi->delete('offset');
+
+my @packages = $cust_main->$method( {
+  'select'    => "$cust_pkg_fields, $part_pkg_fields, $num_svcs",
+  'addl_from' => $addl_from. "
+    LEFT JOIN part_pkg_option AS setup_option
+      ON (     cust_pkg.pkgpart = setup_option.pkgpart
+           AND setup_option.optionname = 'setup_fee' )
+    LEFT JOIN part_pkg_option AS recur_option
+      ON (     cust_pkg.pkgpart = recur_option.pkgpart
+           AND recur_option.optionname = 'recur_fee' )
+  ",
+  'extra_sql' => $extra_sql,
+  'order_by'  => "ORDER BY pkgnum ASC LIMIT $maxrecords OFFSET $offset",
+} );
+
+foreach my $cust_pkg ( @packages ) {
+
+  my %hash = $cust_pkg->hash;
+  my %part_pkg = map  { /^part_pkg_(.+)$/ or die; ( $1 => $hash{$_} ); }
+                 grep { /^part_pkg_/ } keys %hash;
+  $cust_pkg->{'_pkgpart'} = new FS::part_pkg \%part_pkg;
+
+  #arrayref of supplementals
+  $cust_pkg->set('_supplemental', [
+    qsearch('cust_pkg', { main_pkgnum=>$cust_pkg->pkgnum })
+  ] );
+
+  #for future changes
+  if ( $cust_pkg->change_to_pkgnum ) {
+    my $change_to =
+      qsearchs('cust_pkg', { pkgnum=>$cust_pkg->change_to_pkgnum });
+    $cust_pkg->set('change_to_pkg', $change_to);
+    $change_to->set('change_from_pkg', $cust_pkg);
   }
-}
 
-sub pkg_datestr {
-  my($pkg, $field, $conf) = @_ or return '';
-  return '&nbsp;' unless $pkg->{$field};
-  my $format = $conf->exists('pkg_showtimes')
-               ? '<B>%D</B>&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
-               : '<B>%b&nbsp;%o,&nbsp;%Y</B>';
-  ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g;
-  $strip;
-}
+  #for past changes
+  setfrom($cust_pkg);
 
-sub pkg_change_link {
-  my $pkg = shift or return '';
-  return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">!.
-         qq!Change&nbsp;package</a>!;
-}
+  $cust_pkg->{'_cust_pkg_discount_active'} =
+   [ $cust_pkg->cust_pkg_discount_active ];
 
-sub pkg_suspend_link {
-  my $pkg = shift or return '';
-  return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!;
 }
 
-sub pkg_unsuspend_link {
-  my $pkg = shift or return '';
-  return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!;
-}
+sub setfrom {
+  my $cust_pkg = shift;
 
-sub pkg_cancel_link {
-  my $pkg = shift or return '';
-  qq!<A HREF="javascript:areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}', !.
-  qq!'Permanently delete included services and cancel this package?')">!.
-  qq!Cancel now</A> | !.
-  qq!<A HREF="${p}misc/expire_pkg.cgi?$pkg->{pkgnum}">Cancel later</A>!;
-}
+  if ( $cust_pkg->change_pkgnum ) {
+    my $changed_from =
+      qsearchs('cust_pkg', { pkgnum=>$cust_pkg->change_pkgnum });
+    $cust_pkg->set('changed_from_pkg', $changed_from);
+    $changed_from->set('changed_to_pkg', $cust_pkg);
 
-sub pkg_dates_link {
-  my $pkg = shift or return '';
-  qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit&nbsp;dates</A>!;
-}
+    setfrom($changed_from);
+
+  }
 
-sub pkg_customize_link {
-  my $pkg = shift or return '';
-  my $custnum = shift;
-  qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};!.
-  qq!pkgnum=$pkg->{pkgnum}">Customize</A>!;
 }
 
-%>
+</%init>