package FS::part_pkg;
+use base qw( FS::m2m_Common FS::o2m_Common FS::option_Common );
use strict;
-use vars qw( @ISA %plans $DEBUG $setup_hack $skip_pkg_svc_hack );
+use vars qw( %plans $DEBUG $setup_hack $skip_pkg_svc_hack );
use Carp qw(carp cluck confess);
use Scalar::Util qw( blessed );
-use Time::Local qw( timelocal_nocheck );
+use Time::Local qw( timelocal timelocal_nocheck );
use Tie::IxHash;
use FS::Conf;
use FS::Record qw( qsearch qsearchs dbh dbdef );
use FS::part_pkg_option;
use FS::pkg_class;
use FS::agent;
+use FS::part_pkg_msgcat;
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;
+use FS::part_pkg_usage;
use FS::part_pkg_vendor;
-@ISA = qw( FS::m2m_Common FS::option_Common );
$DEBUG = 0;
$setup_hack = 0;
$skip_pkg_svc_hack = 0;
ancestor of this record. If this record is not a successor to another
part_pkg, will be equal to pkgpart.
+=item delay_start - Number of days to delay package start, by default
+
=back
=head1 METHODS
? shift
: { @_ };
- $options->{options} = {} unless defined($options->{options});
+ $options->{options} = { $old->options } unless defined($options->{options});
warn "FS::part_pkg::replace called on $new to replace $old with options".
join(', ', map "$_ => ". $options->{$_}, keys %$options)
}
warn " replacing pkg_svc records" if $DEBUG;
- my $pkg_svc = $options->{'pkg_svc'} || {};
+ 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
- )
- ? 'Y'
- : '';
+ 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 $primary_svc =
+ ( defined($options->{'primary_svc'}) && $options->{'primary_svc'}
+ && $options->{'primary_svc'} == $part_svc->svcpart
+ )
+ ? 'Y'
+ : '';
- my $old_pkg_svc = qsearchs('pkg_svc', {
- 'pkgpart' => $old->pkgpart,
- 'svcpart' => $part_svc->svcpart,
+ my $old_pkg_svc = qsearchs('pkg_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;
}
- );
- 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 : '' ),
- 'pkgpart' => $new->pkgpart,
- 'svcpart' => $part_svc->svcpart,
- 'quantity' => $quantity,
- 'primary_svc' => $primary_svc,
- 'hidden' => $hidden,
- } );
- my $error = $old_pkg_svc
- ? $new_pkg_svc->replace($old_pkg_svc)
- : $new_pkg_svc->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
+
+ 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 : '' ),
+ 'pkgpart' => $new->pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ 'quantity' => $quantity,
+ 'primary_svc' => $primary_svc,
+ 'hidden' => $hidden,
+ } );
+ my $error = $old_pkg_svc
+ ? $new_pkg_svc->replace($old_pkg_svc)
+ : $new_pkg_svc->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ } #foreach $part_svc
+ } #if $options->{pkg_svc}
my @part_pkg_vendor = $old->part_pkg_vendor;
my @current_exportnum = ();
)
|| $self->ut_numbern('fcc_ds0s')
|| $self->ut_numbern('fcc_voip_class')
+ || $self->ut_numbern('delay_start')
|| $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart')
|| $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart')
|| $self->SUPER::check
join("\n", @error);
}
+=item pkg_locale LOCALE
+
+Returns a customer-viewable string representing this package for the given
+locale, from the part_pkg_msgcat table. If the given locale is empty or no
+localized string is found, returns the base pkg field.
+
+=cut
+
+sub pkg_locale {
+ my( $self, $locale ) = @_;
+ return $self->pkg unless $locale;
+ my $part_pkg_msgcat = $self->part_pkg_msgcat($locale) or return $self->pkg;
+ $part_pkg_msgcat->pkg;
+}
+
+=item part_pkg_msgcat LOCALE
+
+Like pkg_locale, but returns the FS::part_pkg_msgcat object itself.
+
+=cut
+
+sub part_pkg_msgcat {
+ my( $self, $locale ) = @_;
+ qsearchs( 'part_pkg_msgcat', {
+ pkgpart => $self->pkgpart,
+ locale => $locale,
+ });
+}
+
=item pkg_comment [ OPTION => VALUE... ]
Returns an (internal) string representing this package. Currently,
}
}
+# whether the plan allows discounts to be applied to this package
sub can_discount { 0; }
+# whether the plan allows changing the start date
sub can_start_date { 1; }
+# the default start date; takes an FS::cust_main as an argument
+sub default_start_date {
+ my $self = shift;
+ my $cust_main = shift;
+ my $conf = FS::Conf->new;
+
+ if ( $self->delay_start ) {
+ my $delay = $self->delay_start;
+
+ my ($mday,$mon,$year) = (localtime(time))[3,4,5];
+ my $start_date = timelocal(0,0,0,$mday,$mon,$year) + 86400 * $delay;
+ return $start_date;
+
+ } elsif ( $conf->exists('order_pkg-no_start_date') ) {
+
+ return '';
+
+ } elsif ( $cust_main ) {
+
+ return $cust_main->next_bill_date;
+
+ } else {
+
+ return '';
+
+ }
+}
+
sub freqs_href {
# moved to FS::Misc to make this accessible to other packages
# at initialization
if ( $freq =~ /^\d+$/ ) {
$mon += $freq;
until ( $mon < 12 ) { $mon -= 12; $year++; }
+
+ $mday = 28 if $mday > 28 && FS::Conf->new->exists('anniversary-rollback');
+
} elsif ( $freq =~ /^(\d+)w$/ ) {
my $weeks = $1;
$mday += $weeks * 7;
shift->_part_pkg_link('svc', @_);
}
+=item supp_part_pkg_link
+
+Returns the associated part_pkg_link records of type 'supp' (supplemental
+packages).
+
+=cut
+
+sub supp_part_pkg_link {
+ shift->_part_pkg_link('supp', @_);
+}
+
sub _part_pkg_link {
my( $self, $type ) = @_;
qsearch({ table => 'part_pkg_link',
qsearch('part_pkg_discount', { 'pkgpart' => $self->pkgpart });
}
+=item part_pkg_usage
+
+Returns the voice usage pools (see L<FS::part_pkg_usage>) defined for
+this package.
+
+=cut
+
+sub part_pkg_usage {
+ my $self = shift;
+ qsearch('part_pkg_usage', { 'pkgpart' => $self->pkgpart });
+}
+
=item _rebless
Reblesses the object into the FS::part_pkg::PLAN class (if available), where
sprintf('%.2f', $self->recur_cost / $self->freq );
}
+=item cust_bill_pkg_recur CUST_PKG
+
+Actual recurring charge for the specified customer package from customer's most
+recent invoice
+
+=cut
+
+sub cust_bill_pkg_recur {
+ my($self, $cust_pkg) = @_;
+ my $cust_bill_pkg = qsearchs({
+ 'table' => 'cust_bill_pkg',
+ 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum )',
+ 'hashref' => { 'pkgnum' => $cust_pkg->pkgnum,
+ 'recur' => { op=>'>', value=>'0' },
+ },
+ 'order_by' => 'ORDER BY cust_bill._date DESC,
+ cust_bill_pkg.sdate DESC
+ LIMIT 1
+ ',
+ }) or return 0; #die "use cust_bill_pkg_recur credits with once_perinv condition";
+ $cust_bill_pkg->recur;
+}
+
=item format OPTION DATA
Returns data formatted according to the function 'format' described