);
use strict;
-use vars qw( %plans $DEBUG $setup_hack $skip_pkg_svc_hack );
+use vars qw( %plans $DEBUG $setup_hack $skip_pkg_svc_hack
+ $cache_enabled %cache_link %cache_pkg_svc
+ );
use Carp qw(carp cluck confess);
use Scalar::Util qw( blessed );
use DateTime;
use FS::part_svc_link;
$DEBUG = 0;
+
$setup_hack = 0;
$skip_pkg_svc_hack = 0;
+$cache_enabled = 0;
+%cache_link = ();
+%cache_pkg_svc = ();
+
=head1 NAME
FS::part_pkg - Object methods for part_pkg objects
ordered. The package will not start billing or have a setup fee charged
until it is manually unsuspended.
+=item change_to_pkgpart - When this package is ordered, schedule a future
+package change. The 'expire_months' field will determine when the package
+change occurs.
+
+=item expire_months - Number of months until this package expires (or changes
+to another package).
+
+=item adjourn_months - Number of months until this package becomes suspended.
+
+=item contract_end_months - Number of months until the package's contract
+ends.
+
=back
=head1 METHODS
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. I<hidden_svc> can
be set to a hashref of svcparts and flag values ('Y' or '') to set the
-'hidden' field in these records.
+'hidden' field in these records, and I<provision_hold> can be set similarly
+for the 'provision_hold' 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.
warn " inserting pkg_svc records" if $DEBUG;
my $pkg_svc = $options{'pkg_svc'} || {};
my $hidden_svc = $options{'hidden_svc'} || {};
+ my $provision_hold = $options{'provision_hold'} || {};
foreach my $part_svc ( qsearch('part_svc', {} ) ) {
my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
my $primary_svc =
'quantity' => $quantity,
'primary_svc' => $primary_svc,
'hidden' => $hidden_svc->{$part_svc->svcpart},
+ 'provision_hold' => $provision_hold->{$part_svc->svcpart},
} );
my $error = $pkg_svc->insert;
if ( $error ) {
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<hidden_svc>, I<primary_svc>
-and I<options>
+Currently available options are: I<pkg_svc>, I<hidden_svc>, I<primary_svc>,
+I<bulk_skip>, I<provision_hold> 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. I<hidden_svc>
can be set to a hashref of svcparts and flag values ('Y' or '') to set the
-'hidden' field in these records. I<bulk_skip> can be set to a hashref of
-svcparts and flag values ('Y' or '') to set the 'bulk_skip' field in those
-records.
+'hidden' field in these records. I<bulk_skip> and I<provision_hold> can be set
+to a hashref of svcparts and flag values ('Y' or '') to set the respective field
+in those records.
If I<primary_svc> is set to the svcpart of the primary service, the appropriate
FS::pkg_svc record will be updated.
my $pkg_svc = $options->{'pkg_svc'};
my $hidden_svc = $options->{'hidden_svc'} || {};
my $bulk_skip = $options->{'bulk_skip'} || {};
+ my $provision_hold = $options->{'provision_hold'} || {};
if ( $pkg_svc ) { # if it wasn't passed, don't change existing pkg_svcs
foreach my $part_svc ( qsearch('part_svc', {} ) ) {
my $quantity = $pkg_svc->{$part_svc->svcpart} || 0;
my $hidden = $hidden_svc->{$part_svc->svcpart} || '';
my $bulk_skip = $bulk_skip->{$part_svc->svcpart} || '';
+ my $provision_hold = $provision_hold->{$part_svc->svcpart} || '';
my $primary_svc =
( defined($options->{'primary_svc'}) && $options->{'primary_svc'}
&& $options->{'primary_svc'} == $part_svc->svcpart
my $old_primary_svc = '';
my $old_hidden = '';
my $old_bulk_skip = '';
+ my $old_provision_hold = '';
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;
- $old_bulk_skip = $old_pkg_svc->old_bulk_skip;
+ $old_bulk_skip = $old_pkg_svc->old_bulk_skip; # should this just be bulk_skip?
+ $old_provision_hold = $old_pkg_svc->provision_hold;
}
next unless $old_quantity != $quantity
|| $old_primary_svc ne $primary_svc
|| $old_hidden ne $hidden
- || $old_bulk_skip ne $bulk_skip;
+ || $old_bulk_skip ne $bulk_skip
+ || $old_provision_hold ne $provision_hold;
my $new_pkg_svc = new FS::pkg_svc( {
'pkgsvcnum' => ( $old_pkg_svc ? $old_pkg_svc->pkgsvcnum : '' ),
'primary_svc' => $primary_svc,
'hidden' => $hidden,
'bulk_skip' => $bulk_skip,
+ 'provision_hold' => $provision_hold,
} );
my $error = $old_pkg_svc
? $new_pkg_svc->replace($old_pkg_svc)
|| $self->ut_numbern('delay_start')
|| $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart')
|| $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart')
+ || $self->ut_numbern('expire_months')
+ || $self->ut_numbern('adjourn_months')
+ || $self->ut_numbern('contract_end_months')
+ || $self->ut_numbern('change_to_pkgpart')
+ || $self->ut_foreign_keyn('change_to_pkgpart', 'part_pkg', 'pkgpart')
|| $self->ut_alphan('agent_pkgpartid')
|| $self->SUPER::check
;
'';
}
+=item check_options
+
+For a passed I<$options> hashref, validates any options that
+have 'validate' subroutines defined (I<$options> values might
+be altered.) Returns error message, or empty string if valid.
+
+Invoked by L</insert> and L</replace> via the equivalent
+methods in L<FS::option_Common>.
+
+=cut
+
+sub check_options {
+ my ($self,$options) = @_;
+ foreach my $option (keys %$options) {
+ if (exists $plans{ $self->plan }->{fields}->{$option}) {
+ if (exists($plans{$self->plan}->{fields}->{$option}->{'validate'})) {
+ # pass option name for use in error message
+ # pass a reference to the $options value, so it can be cleaned up
+ my $error = &{$plans{$self->plan}->{fields}->{$option}->{'validate'}}($option,\($options->{$option}));
+ return $error if $error;
+ }
+ } # else "option does not exist" error?
+ }
+ return '';
+}
+
=item check_pkg_svc
Checks pkg_svc records as a whole (for part_svc_link dependencies).
sub pkg_svc {
my $self = shift;
+ return @{ $cache_pkg_svc{$self->pkgpart} }
+ if $cache_enabled && $cache_pkg_svc{$self->pkgpart};
+
# #sort { $b->primary cmp $a->primary }
# grep { $_->quantity }
# qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
my $opt = ref($_[0]) ? $_[0] : { @_ };
- my %pkg_svc = map { $_->svcpart => $_ }
- grep { $_->quantity }
- qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+ my %pkg_svc = map { $_->svcpart => $_ } $self->_pkg_svc;
unless ( $opt->{disable_linked} ) {
foreach my $dst_pkg ( map $_->dst_pkg, $self->svc_part_pkg_link ) {
- my @pkg_svc = grep { $_->quantity }
- qsearch( 'pkg_svc', { pkgpart=>$dst_pkg->pkgpart } );
+ my @pkg_svc = $dst_pkg->_pkg_svc;
foreach my $pkg_svc ( @pkg_svc ) {
if ( $pkg_svc{$pkg_svc->svcpart} ) {
my $quantity = $pkg_svc{$pkg_svc->svcpart}->quantity;
}
}
- values(%pkg_svc);
+ my @pkg_svc = values(%pkg_svc);
+
+ $cache_pkg_svc{$self->pkgpart} = \@pkg_svc if $cache_enabled;
+
+ @pkg_svc;
}
+sub _pkg_svc {
+ my $self = shift;
+ grep { $_->quantity }
+ qsearch({
+ 'select' => 'pkg_svc.*, part_svc.*',
+ 'table' => 'pkg_svc',
+ 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart )',
+ 'hashref' => { 'pkgpart' => $self->pkgpart },
+ });
+}
+
=item svcpart [ SVCDB ]
Returns the svcpart of the primary service definition (see L<FS::part_svc>)
my( $self, $opt, $ornull ) = @_;
#cache: was pulled up in the original part_pkg query
- if ( $opt =~ /^(setup|recur)_fee$/ && defined($self->hashref->{"_$opt"}) ) {
- return $self->hashref->{"_$opt"};
- }
+ return $self->hashref->{"_opt_$opt"}
+ if exists $self->hashref->{"_opt_$opt"};
- cluck "$self -> option: searching for $opt"
- if $DEBUG;
+ cluck "$self -> option: searching for $opt" if $DEBUG;
my $part_pkg_option =
qsearchs('part_pkg_option', {
pkgpart => $self->pkgpart,
my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
split("\n", $self->get('plandata') );
return $plandata{$opt} if exists $plandata{$opt};
+
+ # check whether the option is defined in plan info (if so, don't warn)
+ if (exists $plans{ $self->plan }->{fields}->{$opt}) {
+ return '';
+ }
cluck "WARNING: (pkgpart ". $self->pkgpart. ") Package def option $opt ".
"not found in options or plandata!\n"
unless $ornull;
sub _part_pkg_link {
my( $self, $type ) = @_;
- 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",
- });
+
+ return @{ $cache_link{$type}->{$self->pkgpart} }
+ if $cache_enabled && $cache_link{$type}->{$self->pkgpart};
+
+ cluck $type.'_part_pkg_link called' if $DEBUG;
+
+ my @ppl =
+ 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",
+ });
+
+ $cache_link{$type}->{$self->pkgpart} = \@ppl if $cache_enabled;
+
+ return @ppl;
}
sub self_and_bill_linked {
Returns the voice usage pools (see L<FS::part_pkg_usage>) defined for
this package.
+=item change_to_pkg
+
+Returns the automatic transfer target for this package, or an empty string
+if there isn't one.
+
+=cut
+
+sub change_to_pkg {
+ my $self = shift;
+ my $pkgpart = $self->change_to_pkgpart or return '';
+ FS::part_pkg->by_key($pkgpart);
+}
+
=item _rebless
Reblesses the object into the FS::part_pkg::PLAN class (if available), where
FS::upgrade_journal->set_done($upgrade);
}
+ # migrate adjourn_months, expire_months, and contract_end_months to
+ # real fields
+ foreach my $field (qw(adjourn_months expire_months contract_end_months)) {
+ foreach my $option (qsearch('part_pkg_option', { optionname => $field })) {
+ my $part_pkg = $option->part_pkg;
+ my $error = $option->delete;
+ if ( $option->optionvalue and $part_pkg->get($field) eq '' ) {
+ $part_pkg->set($field, $option->optionvalue);
+ $error ||= $part_pkg->replace;
+ }
+ die $error if $error;
+ }
+ }
}
=item curuser_pkgs_sql
}
+=item join_options_sql
+
+Returns an SQL fragment for JOINing the part_pkg_option records for this
+package's setup_fee and recur_fee (as setup_option and recur_option,
+respectively). Useful for optimization.
+
+=cut
+
+sub join_options_sql {
+ #my $class = shift;
+ "
+ LEFT JOIN part_pkg_option AS setup_option
+ ON ( part_pkg.pkgpart = setup_option.pkgpart
+ AND setup_option.optionname = 'setup_fee' )
+ LEFT JOIN part_pkg_option AS recur_option
+ ON ( part_pkg.pkgpart = recur_option.pkgpart
+ AND recur_option.optionname = 'recur_fee' )
+ ";
+}
+
=back
=head1 SUBROUTINES