show package pricing details on customer view, RT11372
[freeside.git] / FS / FS / part_pkg.pm
index c08188b..4516816 100644 (file)
@@ -20,6 +20,7 @@ use FS::part_pkg_taxrate;
 use FS::part_pkg_taxoverride;
 use FS::part_pkg_taxproduct;
 use FS::part_pkg_link;
+use FS::part_pkg_discount;
 
 @ISA = qw( FS::m2m_Common FS::option_Common );
 $DEBUG = 0;
@@ -143,7 +144,9 @@ Currently available options are: I<pkg_svc>, I<primary_svc>, I<cust_pkg>,
 I<custnum_ref> and I<options>.
 
 If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
-values, appropriate FS::pkg_svc records will be inserted.
+values, appropriate FS::pkg_svc records will be inserted.  I<hidden_svc> can 
+be set to a hashref of svcparts and flag values ('Y' or '') to set the 
+'hidden' field in these records.
 
 If I<primary_svc> is set to the svcpart of the primary service, the appropriate
 FS::pkg_svc record will be updated.
@@ -229,6 +232,7 @@ sub insert {
 
     warn "  inserting pkg_svc records" if $DEBUG;
     my $pkg_svc = $options{'pkg_svc'} || {};
+    my $hidden_svc = $options{'hidden_svc'} || {};
     foreach my $part_svc ( qsearch('part_svc', {} ) ) {
       my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
       my $primary_svc =
@@ -241,6 +245,7 @@ sub insert {
         'svcpart'     => $part_svc->svcpart,
         'quantity'    => $quantity, 
         'primary_svc' => $primary_svc,
+        'hidden'      => $hidden_svc->{$part_svc->svcpart},
       } );
       my $error = $pkg_svc->insert;
       if ( $error ) {
@@ -292,10 +297,13 @@ sub delete {
 Replaces OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
-Currently available options are: I<pkg_svc>, I<primary_svc> and I<options>
+Currently available options are: I<pkg_svc>, I<hidden_svc>, I<primary_svc> 
+and I<options>
 
 If I<pkg_svc> is set to a hashref with svcparts as keys and quantities as
-values, the appropriate FS::pkg_svc records will be replaced.
+values, the appropriate FS::pkg_svc records will be replaced.  I<hidden_svc>
+can be set to a hashref of svcparts and flag values ('Y' or '') to set the 
+'hidden' field in these records.
 
 If I<primary_svc> is set to the svcpart of the primary service, the appropriate
 FS::pkg_svc record will be updated.
@@ -378,8 +386,10 @@ sub replace {
 
   warn "  replacing pkg_svc records" if $DEBUG;
   my $pkg_svc = $options->{'pkg_svc'} || {};
+  my $hidden_svc = $options->{'hidden_svc'} || {};
   foreach my $part_svc ( qsearch('part_svc', {} ) ) {
     my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
+    my $hidden = $hidden_svc->{$part_svc->svcpart} || '';
     my $primary_svc =
       ( defined($options->{'primary_svc'}) && $options->{'primary_svc'}
         && $options->{'primary_svc'} == $part_svc->svcpart
@@ -387,17 +397,24 @@ sub replace {
         ? 'Y'
         : '';
 
-
     my $old_pkg_svc = qsearchs('pkg_svc', {
-      'pkgpart' => $old->pkgpart,
-      'svcpart' => $part_svc->svcpart,
-    } );
-    my $old_quantity = $old_pkg_svc ? $old_pkg_svc->quantity : 0;
-    my $old_primary_svc =
-      ( $old_pkg_svc && $old_pkg_svc->dbdef_table->column('primary_svc') )
-        ? $old_pkg_svc->primary_svc
-        : '';
-    next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc;
+        'pkgpart' => $old->pkgpart,
+        'svcpart' => $part_svc->svcpart,
+      }
+    );
+    my $old_quantity = 0;
+    my $old_primary_svc = '';
+    my $old_hidden = '';
+    if ( $old_pkg_svc ) {
+      $old_quantity = $old_pkg_svc->quantity;
+      $old_primary_svc = $old_pkg_svc->primary_svc 
+        if $old_pkg_svc->dbdef_table->column('primary_svc'); # is this needed?
+      $old_hidden = $old_pkg_svc->hidden;
+    }
+    next unless $old_quantity != $quantity || 
+                $old_primary_svc ne $primary_svc ||
+                $old_hidden ne $hidden;
   
     my $new_pkg_svc = new FS::pkg_svc( {
       'pkgsvcnum'   => ( $old_pkg_svc ? $old_pkg_svc->pkgsvcnum : '' ),
@@ -405,6 +422,7 @@ sub replace {
       'svcpart'     => $part_svc->svcpart,
       'quantity'    => $quantity, 
       'primary_svc' => $primary_svc,
+      'hidden'      => $hidden,
     } );
     my $error = $old_pkg_svc
                   ? $new_pkg_svc->replace($old_pkg_svc)
@@ -519,9 +537,13 @@ sub pkg_comment {
   $pre. $self->pkg. ' - '. $self->custom_comment;
 }
 
+sub price_info { # safety, in case a part_pkg hasn't defined price_info
+    '';
+}
+
 sub custom_comment {
   my $self = shift;
-  ( $self->custom ? '(CUSTOM) ' : '' ). $self->comment;
+  ( $self->custom ? '(CUSTOM) ' : '' ). $self->comment . ' ' . $self->price_info;
 }
 
 =item pkg_class
@@ -822,7 +844,7 @@ argument.
 
 sub add_freq {
   my( $self, $date, $freq ) = @_;
-  $freq = $self->freq if !defined($freq);
+  $freq = $self->freq unless $freq;
 
   #change this bit to use Date::Manip? CAREFUL with timezones (see
   # mailing list archive)
@@ -940,6 +962,8 @@ sub _part_pkg_link {
   qsearch({ table    => 'part_pkg_link',
             hashref  => { 'src_pkgpart' => $self->pkgpart,
                           'link_type'   => $type,
+                          #protection against infinite recursive links
+                          'dst_pkgpart' => { op=>'!=', value=> $self->pkgpart },
                         },
             order_by => "ORDER BY hidden",
          });
@@ -1126,6 +1150,18 @@ sub part_pkg_taxrate {
          } );
 }
 
+=item part_pkg_discount
+
+Returns the package to discount m2m records (see L<FS::part_pkg_discount>)
+for this package.
+
+=cut
+
+sub part_pkg_discount {
+  my $self = shift;
+  qsearch('part_pkg_discount', { 'pkgpart' => $self->pkgpart });
+}
+
 =item _rebless
 
 Reblesses the object into the FS::part_pkg::PLAN class (if available), where
@@ -1336,6 +1372,25 @@ sub _upgrade_data { # class method
     die $error if $error;
   }
 
+  my @part_pkg_option = qsearch('part_pkg_option',
+    { 'optionname'  => 'unused_credit',
+      'optionvalue' => 1,
+    });
+  foreach my $old_opt (@part_pkg_option) {
+    my $pkgpart = $old_opt->pkgpart;
+    my $error = $old_opt->delete;
+    die $error if $error;
+
+    foreach (qw(unused_credit_cancel unused_credit_change)) {
+      my $new_opt = new FS::part_pkg_option {
+        'pkgpart'     => $pkgpart,
+        'optionname'  => $_,
+        'optionvalue' => 1,
+      };
+      $error = $new_opt->insert;
+      die $error if $error;
+    }
+  }
 }
 
 =item curuser_pkgs_sql
@@ -1422,19 +1477,48 @@ foreach my $INC ( @INC ) {
       next;
     }
     warn "got plan info from FS::part_pkg::$mod: $info\n" if $DEBUG;
-    if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
-      warn "skipping disabled plan FS::part_pkg::$mod" if $DEBUG;
-      next;
-    }
+    #if ( exists($info->{'disabled'}) && $info->{'disabled'} ) {
+    #  warn "skipping disabled plan FS::part_pkg::$mod" if $DEBUG;
+    #  next;
+    #}
     $info{$mod} = $info;
+    $info->{'weight'} ||= 0; # quiet warnings
   }
 }
 
+# copy one level deep to allow replacement of fields and fieldorder
 tie %plans, 'Tie::IxHash',
-  map  { $_ => $info{$_} }
+  map  { my %infohash = %{ $info{$_} }; 
+          $_ => \%infohash }
   sort { $info{$a}->{'weight'} <=> $info{$b}->{'weight'} }
   keys %info;
 
+# inheritance of plan options
+foreach my $name (keys(%info)) {
+  if (exists($info{$name}->{'disabled'}) and $info{$name}->{'disabled'}) {
+    warn "skipping disabled plan FS::part_pkg::$name" if $DEBUG;
+    delete $plans{$name};
+    next;
+  }
+  my $parents = $info{$name}->{'inherit_fields'} || [];
+  my (%fields, %field_exists, @fieldorder);
+  foreach my $parent ($name, @$parents) {
+    %fields = ( # avoid replacing existing fields
+      %{ $info{$parent}->{'fields'} || {} },
+      %fields
+    );
+    foreach (@{ $info{$parent}->{'fieldorder'} || [] }) {
+      # avoid duplicates
+      next if $field_exists{$_};
+      $field_exists{$_} = 1;
+      # allow inheritors to remove inherited fields from the fieldorder
+      push @fieldorder, $_ if !exists($fields{$_}->{'disabled'});
+    }
+  }
+  $plans{$name}->{'fields'} = \%fields;
+  $plans{$name}->{'fieldorder'} = \@fieldorder;
+}
+
 sub plan_info {
   \%plans;
 }