X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_pkg.pm;h=061001bdcd2e7ec65e34d2d3b51d714bd475fd76;hb=98aa4355cb0a31f97cdbeabb6c190c908ba355a0;hp=c4859476fb7b89c1296f4613922bcf2d408be20c;hpb=f0f31ac563d2e3a4e70092845ee635eab0ee30c9;p=freeside.git diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index c4859476f..061001bdc 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -103,6 +103,13 @@ inherits from FS::Record. The following fields are currently supported: =item fcc_ds0s - Optional DS0 equivalency number for FCC form 477 +=item successor - Foreign key for the part_pkg that replaced this record. +If this record is not obsolete, will be null. + +=item family_pkgpart - Foreign key for the part_pkg that was the earliest +ancestor of this record. If this record is not a successor to another +part_pkg, will be equal to pkgpart. + =back =head1 METHODS @@ -192,6 +199,16 @@ sub insert { return $error; } + # set family_pkgpart + if ( $self->get('family_pkgpart') eq '' ) { + $self->set('family_pkgpart' => $self->pkgpart); + $error = $self->SUPER::replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + my $conf = new FS::Conf; if ( $conf->exists('agent_defaultpkg') ) { warn " agent_defaultpkg set; allowing all agents to purchase package" @@ -294,7 +311,7 @@ sub insert { } } - warn " commiting transaction" if $DEBUG; + warn " committing transaction" if $DEBUG and $oldAutoCommit; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -360,6 +377,28 @@ sub replace { my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; + + my $conf = new FS::Conf; + if ( $conf->exists('part_pkg-lineage') ) { + if ( grep { $options->{options}->{$_} ne $old->option($_, 1) } + qw(setup_fee recur_fee) #others? config? + ) { + + warn " superseding package" if $DEBUG; + + my $error = $new->supersede($old, %$options); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + else { + warn " committing transaction" if $DEBUG and $oldAutoCommit; + $dbh->commit if $oldAutoCommit; + return $error; + } + } + #else nothing + } #plandata shit stays in replace for upgrades until after 2.0 (or edit #_upgrade_data) @@ -501,8 +540,18 @@ sub replace { } } } + + # propagate changes to certain core fields + if ( $conf->exists('part_pkg-lineage') ) { + warn " propagating changes to family" if $DEBUG; + my $error = $new->propagate($old); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } - warn " commiting transaction" if $DEBUG; + warn " committing transaction" if $DEBUG and $oldAutoCommit; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -573,6 +622,8 @@ sub check { : $self->ut_agentnum_acl('agentnum', \@null_agentnum_right) ) || $self->ut_numbern('fcc_ds0s') + || $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart') + || $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart') || $self->SUPER::check ; return $error if $error; @@ -587,6 +638,76 @@ sub check { ''; } +=item supersede OLD [, OPTION => VALUE ... ] + +Inserts this package as a successor to the package OLD. All options are as +for C. After inserting, disables OLD and sets the new package as its +successor. + +=cut + +sub supersede { + my ($new, $old, %options) = @_; + my $error; + + $new->set('pkgpart' => ''); + $new->set('family_pkgpart' => $old->family_pkgpart); + warn " inserting successor package\n" if $DEBUG; + $error = $new->insert(%options); + return $error if $error; + + warn " disabling superseded package\n" if $DEBUG; + $old->set('successor' => $new->pkgpart); + $old->set('disabled' => 'Y'); + $error = $old->SUPER::replace; # don't change its options/pkg_svc records + return $error if $error; + + warn " propagating changes to family" if $DEBUG; + $new->propagate($old); +} + +=item propagate OLD + +If any of certain fields have changed from OLD to this package, then, +for all packages in the same lineage as this one, sets those fields +to their values in this package. + +=cut + +my @propagate_fields = ( + qw( pkg classnum setup_cost recur_cost taxclass + setuptax recurtax pay_weight credit_weight + ) +); + +sub propagate { + my $new = shift; + my $old = shift; + my %fields = ( + map { $_ => $new->get($_) } + grep { $new->get($_) ne $old->get($_) } + @propagate_fields + ); + + my @part_pkg = qsearch('part_pkg', { + 'family_pkgpart' => $new->family_pkgpart + }); + my @error; + foreach my $part_pkg ( @part_pkg ) { + my $pkgpart = $part_pkg->pkgpart; + next if $pkgpart == $new->pkgpart; # don't modify $new + warn " propagating to pkgpart $pkgpart\n" if $DEBUG; + foreach ( keys %fields ) { + $part_pkg->set($_, $fields{$_}); + } + # SUPER::replace to avoid changing non-core fields + my $error = $part_pkg->SUPER::replace; + push @error, "pkgpart $pkgpart: $error" + if $error; + } + join("\n", @error); +} + =item pkg_comment [ OPTION => VALUE... ] Returns an (internal) string representing this package. Currently, @@ -851,10 +972,7 @@ Returns true if this package is free. sub is_free { my $self = shift; - unless ( $self->plan ) { - $self->setup =~ /^\s*0+(\.0*)?\s*$/ - && $self->recur =~ /^\s*0+(\.0*)?\s*$/; - } elsif ( $self->can('is_free_options') ) { + if ( $self->can('is_free_options') ) { not grep { $_ !~ /^\s*0*(\.0*)?\s*$/ } map { $self->option($_) } $self->is_free_options; @@ -867,6 +985,8 @@ sub is_free { sub can_discount { 0; } +sub can_start_date { 1; } + sub freqs_href { # moved to FS::Misc to make this accessible to other packages # at initialization @@ -1278,52 +1398,31 @@ sub _rebless { } return $self if ref($self) =~ /::$plan$/; #already blessed into plan subclass my $class = ref($self). "::$plan"; - warn "reblessing $self into $class" if $DEBUG; + warn "reblessing $self into $class" if $DEBUG > 1; eval "use $class;"; die $@ if $@; bless($self, $class) unless $@; $self; } -#fallbacks that eval the setup and recur fields, for backwards compat - -sub calc_setup { - my $self = shift; - warn 'no price plan class for '. $self->plan. ", eval-ing setup\n"; - $self->_calc_eval('setup', @_); -} - -sub calc_recur { - my $self = shift; - warn 'no price plan class for '. $self->plan. ", eval-ing recur\n"; - $self->_calc_eval('recur', @_); -} - -use vars qw( $sdate @details ); -sub _calc_eval { - #my( $self, $field, $cust_pkg ) = @_; - my( $self, $field, $cust_pkg, $sdateref, $detailsref ) = @_; - *sdate = $sdateref; - *details = $detailsref; - $self->$field() =~ /^(.*)$/ - or die "Illegal $field (pkgpart ". $self->pkgpart. '): '. - $self->$field(). "\n"; - my $prog = $1; - return 0 if $prog =~ /^\s*$/; - my $value = eval $prog; - die $@ if $@; - $value; -} +#fatal fallbacks +sub calc_setup { die 'no calc_setup for '. shift->plan. "\n"; } +sub calc_recur { die 'no calc_recur for '. shift->plan. "\n"; } #fallback that return 0 for old legacy packages with no plan - sub calc_remain { 0; } -sub calc_cancel { 0; } sub calc_units { 0; } +#fallback for everything not based on flat.pm +sub recur_temporality { 'upcoming'; } +sub calc_cancel { 0; } + #fallback for everything except bulk.pm sub hide_svc_detail { 0; } +#fallback for packages that can't/won't summarize usage +sub sum_usage { 0; } + =item recur_cost_permonth CUST_PKG recur_cost divided by freq (only supported for monthly and longer frequencies) @@ -1390,8 +1489,6 @@ sub _upgrade_data { # class method my @part_pkg = qsearch({ 'table' => 'part_pkg', 'extra_sql' => "WHERE ". join(' OR ', - ( map "($_ IS NOT NULL AND $_ != '' )", - qw( plandata setup recur ) ), 'plan IS NULL', "plan = '' ", ), }); @@ -1402,43 +1499,7 @@ sub _upgrade_data { # class method $part_pkg->plan('flat'); } - if ( length($part_pkg->option('setup_fee')) == 0 - && $part_pkg->setup =~ /^\s*([\d\.]+)\s*$/ ) { - - my $opt = new FS::part_pkg_option { - 'pkgpart' => $part_pkg->pkgpart, - 'optionname' => 'setup_fee', - 'optionvalue' => $1, - }; - my $error = $opt->insert; - die $error if $error; - - - #} else { - # die "Can't parse part_pkg.setup for fee; convert pkgnum ". - # $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n"; - } - $part_pkg->setup(''); - - if ( length($part_pkg->option('recur_fee')) == 0 - && $part_pkg->recur =~ /^\s*([\d\.]+)\s*$/ ) { - - my $opt = new FS::part_pkg_option { - 'pkgpart' => $part_pkg->pkgpart, - 'optionname' => 'recur_fee', - 'optionvalue' => $1, - }; - my $error = $opt->insert; - die $error if $error; - - - #} else { - # die "Can't parse part_pkg.setup for fee; convert pkgnum ". - # $part_pkg->pkgnum. " manually: ". $part_pkg->setup. "\n"; - } - $part_pkg->recur(''); - - $part_pkg->replace; #this should take care of plandata, right? + $part_pkg->replace; } @@ -1470,6 +1531,14 @@ sub _upgrade_data { # class method die $error if $error; } + # set family_pkgpart on any packages that don't have it + @part_pkg = qsearch('part_pkg', { 'family_pkgpart' => '' }); + foreach my $part_pkg (@part_pkg) { + $part_pkg->set('family_pkgpart' => $part_pkg->pkgpart); + my $error = $part_pkg->SUPER::replace; + die $error if $error; + } + my @part_pkg_option = qsearch('part_pkg_option', { 'optionname' => 'unused_credit', 'optionvalue' => 1,