summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2013-07-17 09:04:06 -0700
committerIvan Kohler <ivan@freeside.biz>2013-07-17 09:04:06 -0700
commit91dbe4c3834f38d428367d9a1e2c6cf9ea9d84a4 (patch)
treee4d6a63b75a2e3df13fdd35e24ae98ec8b3567cb /FS
parent2101a32bdf12abdb2afdb654d6da30975ddd4fc9 (diff)
parentd0fcbc3d04250ec54cb5dea7abcc58d1f45d78b1 (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/Conf.pm7
-rw-r--r--FS/FS/Report/Table.pm38
-rw-r--r--FS/FS/Schema.pm2
-rw-r--r--FS/FS/cdr/netsapiens.pm17
-rw-r--r--FS/FS/cust_main/Billing.pm20
-rw-r--r--FS/FS/cust_pkg.pm347
-rw-r--r--FS/FS/part_export/freeswitch.pm2
-rw-r--r--FS/FS/part_export/freeswitch_multifile.pm2
-rw-r--r--FS/FS/part_pkg.pm37
-rw-r--r--FS/FS/part_pkg/delayed_Mixin.pm12
-rw-r--r--FS/FS/pay_batch.pm2
-rw-r--r--FS/FS/pay_batch/eft_canada.pm8
12 files changed, 404 insertions, 90 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index f76c72ff4..ae1fd4be8 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -4349,6 +4349,13 @@ and customer address. Include units.',
},
{
+ 'key' => 'part_pkg-delay_start',
+ 'section' => '',
+ 'description' => 'Enabled "delayed start" option for packages.',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'mcp_svcpart',
'section' => '',
'description' => 'Master Control Program svcpart. Leave this blank.',
diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm
index 2e202e5d9..c5a6503c3 100644
--- a/FS/FS/Report/Table.pm
+++ b/FS/FS/Report/Table.pm
@@ -443,6 +443,7 @@ sub cust_bill_pkg_setup {
my @where = (
'pkgnum != 0',
$self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
+ $self->with_report_option($opt{'report_optionnum'}, $opt{'use_override'}),
$self->in_time_period_and_agent($speriod, $eperiod, $agentnum),
);
@@ -474,6 +475,7 @@ sub cust_bill_pkg_recur {
my @where = (
'pkgnum != 0',
$self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
+ $self->with_report_option($opt{'report_optionnum'}, $opt{'use_override'}),
);
push @where, 'cust_main.refnum = '. $opt{'refnum'} if $opt{'refnum'};
@@ -552,6 +554,7 @@ sub cust_bill_pkg_detail {
push @where,
$self->with_classnum($opt{'classnum'}, $opt{'use_override'}),
$self->with_usageclass($opt{'usageclass'}),
+ $self->with_report_option($opt{'report_optionnum'}, $opt{'use_override'}),
;
if ( $opt{'distribute'} ) {
@@ -733,6 +736,41 @@ sub with_usageclass {
return "cust_bill_pkg_detail.classnum $comparison";
}
+sub with_report_option {
+ my $self = shift;
+ # $num can be a single number, or a comma-delimited list of numbers,
+ # or '0' to match only the empty set.
+ #
+ # or the word 'multiple' for all packages with more than one report class
+ my ($num, $use_override) = @_;
+ return '' if !defined($num);
+
+ # stringify the set of report options for each pkgpart
+ my $table = $use_override ? 'override' : 'part_pkg';
+ my $subselect = "
+ SELECT replace(optionname, 'report_option_', '') AS num
+ FROM part_pkg_option
+ WHERE optionname like 'report_option_%'
+ AND part_pkg_option.pkgpart = $table.pkgpart
+ ORDER BY num";
+
+ my $comparison;
+ if ( $num eq 'multiple' ) {
+ $comparison = "(SELECT COUNT(*) FROM ($subselect) AS x) > 1";
+ } elsif ( $num eq '0' ) {
+ $comparison = "NOT EXISTS ($subselect)";
+ } else {
+ $comparison = "(SELECT COALESCE(string_agg(num, ','), '') FROM (
+ $subselect
+ ) AS x) = '$num'";
+ }
+ if ( $use_override ) {
+ # then also allow the non-override package to match
+ $comparison = "( $comparison OR " . $self->with_report_option($num) . ")";
+ }
+ $comparison;
+}
+
sub scalar_sql {
my( $self, $sql ) = ( shift, shift );
my $sth = dbh->prepare($sql) or die dbh->errstr;
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 2b7db26f3..21af3a4d1 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1826,6 +1826,7 @@ sub tables_hashref {
'waive_setup', 'char', 'NULL', 1, '', '',
'recur_show_zero', 'char', 'NULL', 1, '', '',
'setup_show_zero', 'char', 'NULL', 1, '', '',
+ 'change_to_pkgnum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'pkgnum',
'unique' => [],
@@ -2078,6 +2079,7 @@ sub tables_hashref {
'setup_show_zero', 'char', 'NULL', 1, '', '',
'successor', 'int', 'NULL', '', '', '',
'family_pkgpart','int', 'NULL', '', '', '',
+ 'delay_start', 'int', 'NULL', '', '', '',
],
'primary_key' => 'pkgpart',
'unique' => [],
diff --git a/FS/FS/cdr/netsapiens.pm b/FS/FS/cdr/netsapiens.pm
index bcaa3496d..9d07aef7e 100644
--- a/FS/FS/cdr/netsapiens.pm
+++ b/FS/FS/cdr/netsapiens.pm
@@ -15,11 +15,11 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
'disabled' => 0, #0 default, set to 1 to disable
'import_fields' => [
-
+
sub { my ($cdr, $direction) = @_;
- if ($direction =~ /^o/) { # 'origination'
+ if ($direction =~ /^t/) { # 'origination'
# leave src and dst as they are
- } elsif ($direction =~ /^t/) {
+ } elsif ($direction =~ /^o/) {
my ($local, $remote) = ($cdr->src, $cdr->dst);
$cdr->set('dst', $local);
$cdr->set('src', $remote);
@@ -28,7 +28,7 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
'', #Domain
'', #user
'src', #local party (src/dst, based on direction)
- _cdr_date_parser_maker('startddate'),
+ _cdr_date_parser_maker('startdate'),
_cdr_date_parser_maker('answerdate'),
sub { my ($cdr, $duration) = @_;
$cdr->set('duration', $duration);
@@ -37,14 +37,15 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
if $cdr->answerdate;
},
'dst', #remote party
- '', #dialed number
+ sub { my ($cdr, $dialednum) = @_;
+ $cdr->set('dst',$dialednum) if $dialednum =~ /^(\+?1)?8(8|([02-7])\3)/;
+ }, #dialed number
'uniqueid', #CallID (timestamp + '-' + 32 char hex string)
- 'src_ip_addr',
- 'dst_ip_addr',
+ '',
+ '',
'disposition',
],
);
1;
-
diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm
index 220f66a0c..081dd70f7 100644
--- a/FS/FS/cust_main/Billing.pm
+++ b/FS/FS/cust_main/Billing.pm
@@ -192,14 +192,30 @@ sub cancel_expired_pkgs {
my @errors = ();
- foreach my $cust_pkg ( @cancel_pkgs ) {
+ CUST_PKG: foreach my $cust_pkg ( @cancel_pkgs ) {
my $cpr = $cust_pkg->last_cust_pkg_reason('expire');
- my $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum,
+ my $error;
+
+ if ( $cust_pkg->change_to_pkgnum ) {
+
+ my $new_pkg = FS::cust_pkg->by_key($cust_pkg->change_to_pkgnum);
+ if ( !$new_pkg ) {
+ push @errors, 'can\'t change pkgnum '.$cust_pkg->pkgnum.' to pkgnum '.
+ $cust_pkg->change_to_pkgnum.'; not expiring';
+ next CUST_PKG;
+ }
+ $error = $cust_pkg->change( 'cust_pkg' => $new_pkg,
+ 'unprotect_svcs' => 1 );
+ $error = '' if ref $error eq 'FS::cust_pkg';
+
+ } else { # just cancel it
+ $error = $cust_pkg->cancel($cpr ? ( 'reason' => $cpr->reasonnum,
'reason_otaker' => $cpr->otaker,
'time' => $time,
)
: ()
);
+ }
push @errors, 'pkgnum '.$cust_pkg->pkgnum.": $error" if $error;
}
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index ddfab5dcb..01eaf6253 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -210,6 +210,11 @@ The pkgnum of the package that this package is supplemental to, if any.
The package link (L<FS::part_pkg_link>) that defines this supplemental
package, if it is one.
+=item change_to_pkgnum
+
+The pkgnum of the package this one will be "changed to" in the future
+(on its expiration date).
+
=back
Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date
@@ -289,6 +294,7 @@ sub insert {
my $part_pkg = $self->part_pkg;
+ # if the package def says to start only on the first of the month:
if ( $part_pkg->option('start_1st', 1) && !$self->start_date ) {
my ($sec,$min,$hour,$mday,$mon,$year) = (localtime(time) )[0,1,2,3,4,5];
$mon += 1 unless $mday == 1;
@@ -296,6 +302,8 @@ sub insert {
$self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) );
}
+ # set up any automatic expire/adjourn/contract_end timers
+ # based on the start date
foreach my $action ( qw(expire adjourn contract_end) ) {
my $months = $part_pkg->option("${action}_months",1);
if($months and !$self->$action) {
@@ -304,16 +312,16 @@ sub insert {
}
}
+ # if this package has "free days" and delayed setup fee, tehn
+ # set start date that many days in the future.
+ # (this should have been set in the UI, but enforce it here)
if ( ! $options{'change'}
&& ( my $free_days = $part_pkg->option('free_days',1) )
&& $part_pkg->option('delay_setup',1)
#&& ! $self->start_date
)
{
- my ($mday,$mon,$year) = (localtime(time) )[3,4,5];
- #my $start_date = ($self->start_date || timelocal(0,0,0,$mday,$mon,$year)) + 86400 * $free_days;
- my $start_date = timelocal(0,0,0,$mday,$mon,$year) + 86400 * $free_days;
- $self->start_date($start_date);
+ $self->start_date( $part_pkg->default_start_date );
}
$self->order_date(time);
@@ -350,15 +358,6 @@ sub insert {
}
}
- #if ( $self->reg_code ) {
- # my $reg_code = qsearchs('reg_code', { 'code' => $self->reg_code } );
- # $error = $reg_code->delete;
- # if ( $error ) {
- # $dbh->rollback if $oldAutoCommit;
- # return $error;
- # }
- #}
-
my $conf = new FS::Conf;
if ( $conf->config('ticket_system') && $options{ticket_subject} ) {
@@ -648,6 +647,7 @@ sub check {
|| $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ])
|| $self->ut_foreign_keyn('main_pkgnum', 'cust_pkg', 'pkgnum')
|| $self->ut_foreign_keyn('pkglinknum', 'part_pkg_link', 'pkglinknum')
+ || $self->ut_foreign_keyn('change_to_pkgnum', 'cust_pkg', 'pkgnum')
;
return $error if $error;
@@ -869,10 +869,19 @@ sub cancel {
} #unless $date
my %hash = $self->hash;
- $date ? ($hash{'expire'} = $date) : ($hash{'cancel'} = $cancel_time);
+ if ( $date ) {
+ $hash{'expire'} = $date;
+ } else {
+ $hash{'cancel'} = $cancel_time;
+ }
$hash{'change_custnum'} = $options{'change_custnum'};
+
my $new = new FS::cust_pkg ( \%hash );
$error = $new->replace( $self, options => { $self->options } );
+ if ( $self->change_to_pkgnum ) {
+ my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum);
+ $error ||= $change_to->cancel || $change_to->delete;
+ }
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -1725,15 +1734,27 @@ New pkgpart (see L<FS::part_pkg>).
New refnum (see L<FS::part_referral>).
+=item cust_pkg
+
+"New" (existing) FS::cust_pkg object. The package's services and other
+attributes will be transferred to this package.
+
=item keep_dates
Set to true to transfer billing dates (start_date, setup, last_bill, bill,
susp, adjourn, cancel, expire, and contract_end) to the new package.
+=item unprotect_svcs
+
+Normally, change() will rollback and return an error if some services
+can't be transferred (also see the I<cust_pkg-change_svcpart> config option).
+If unprotect_svcs is true, this method will transfer as many services as
+it can and then unconditionally cancel the old package.
+
=back
-At least one of locationnum, cust_location, pkgpart, refnum must be specified
-(otherwise, what's the point?)
+At least one of locationnum, cust_location, pkgpart, refnum, cust_main, or
+cust_pkg must be specified (otherwise, what's the point?)
Returns either the new FS::cust_pkg object or a scalar error.
@@ -1790,6 +1811,12 @@ sub change {
$opt->{'locationnum'} = $opt->{'cust_location'}->locationnum;
}
+ if ( $opt->{'cust_pkg'} ) {
+ # treat changing to a package with a different pkgpart as a
+ # pkgpart change (because it is)
+ $opt->{'pkgpart'} = $opt->{'cust_pkg'}->pkgpart;
+ }
+
# whether to override pkgpart checking on the new package
my $same_pkgpart = 1;
if ( $opt->{'pkgpart'} and ( $opt->{'pkgpart'} != $self->pkgpart ) ) {
@@ -1841,16 +1868,30 @@ sub change {
$hash{'contactnum'} = $opt->{'contactnum'} if $opt->{'contactnum'};
- # Create the new package.
- my $cust_pkg = new FS::cust_pkg {
- custnum => $custnum,
- pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ),
- refnum => ( $opt->{'refnum'} || $self->refnum ),
- locationnum => ( $opt->{'locationnum'} ),
- %hash,
- };
- $error = $cust_pkg->insert( 'change' => 1,
- 'allow_pkgpart' => $same_pkgpart );
+ my $cust_pkg;
+ if ( $opt->{'cust_pkg'} ) {
+ # The target package already exists; update it to show that it was
+ # changed from this package.
+ $cust_pkg = $opt->{'cust_pkg'};
+
+ foreach ( qw( pkgnum pkgpart locationnum ) ) {
+ $cust_pkg->set("change_$_", $self->get($_));
+ }
+ $cust_pkg->set('change_date', $time);
+ $error = $cust_pkg->replace;
+
+ } else {
+ # Create the new package.
+ $cust_pkg = new FS::cust_pkg {
+ custnum => $custnum,
+ pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ),
+ refnum => ( $opt->{'refnum'} || $self->refnum ),
+ locationnum => ( $opt->{'locationnum'} ),
+ %hash,
+ };
+ $error = $cust_pkg->insert( 'change' => 1,
+ 'allow_pkgpart' => $same_pkgpart );
+ }
if ($error) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -1875,7 +1916,11 @@ sub change {
}
}
- if ($error > 0) {
+ # We set unprotect_svcs when executing a "future package change". It's
+ # not a user-interactive operation, so returning an error means the
+ # package change will just fail. Rather than have that happen, we'll
+ # let leftover services be deleted.
+ if ($error > 0 and !$opt->{'unprotect_svcs'}) {
# Transfers were successful, but we still had services left on the old
# package. We can't change the package under this circumstances, so abort.
$dbh->rollback if $oldAutoCommit;
@@ -1936,57 +1981,62 @@ sub change {
return "Error transferring package notes: $error";
}
}
-
- # Order any supplemental packages.
- my $part_pkg = $cust_pkg->part_pkg;
- my @old_supp_pkgs = $self->supplemental_pkgs;
+
my @new_supp_pkgs;
- foreach my $link ($part_pkg->supp_part_pkg_link) {
- my $old;
- foreach (@old_supp_pkgs) {
- if ($_->pkgpart == $link->dst_pkgpart) {
- $old = $_;
- $_->pkgpart(0); # so that it can't match more than once
+
+ if ( !$opt->{'cust_pkg'} ) {
+ # Order any supplemental packages.
+ my $part_pkg = $cust_pkg->part_pkg;
+ my @old_supp_pkgs = $self->supplemental_pkgs;
+ foreach my $link ($part_pkg->supp_part_pkg_link) {
+ my $old;
+ foreach (@old_supp_pkgs) {
+ if ($_->pkgpart == $link->dst_pkgpart) {
+ $old = $_;
+ $_->pkgpart(0); # so that it can't match more than once
+ }
+ last if $old;
}
- last if $old;
- }
- # false laziness with FS::cust_main::Packages::order_pkg
- my $new = FS::cust_pkg->new({
- pkgpart => $link->dst_pkgpart,
- pkglinknum => $link->pkglinknum,
- custnum => $custnum,
- main_pkgnum => $cust_pkg->pkgnum,
- locationnum => $cust_pkg->locationnum,
- start_date => $cust_pkg->start_date,
- order_date => $cust_pkg->order_date,
- expire => $cust_pkg->expire,
- adjourn => $cust_pkg->adjourn,
- contract_end => $cust_pkg->contract_end,
- refnum => $cust_pkg->refnum,
- discountnum => $cust_pkg->discountnum,
- waive_setup => $cust_pkg->waive_setup,
- });
- if ( $old and $opt->{'keep_dates'} ) {
- foreach (qw(setup bill last_bill)) {
- $new->set($_, $old->get($_));
+ # false laziness with FS::cust_main::Packages::order_pkg
+ my $new = FS::cust_pkg->new({
+ pkgpart => $link->dst_pkgpart,
+ pkglinknum => $link->pkglinknum,
+ custnum => $custnum,
+ main_pkgnum => $cust_pkg->pkgnum,
+ locationnum => $cust_pkg->locationnum,
+ start_date => $cust_pkg->start_date,
+ order_date => $cust_pkg->order_date,
+ expire => $cust_pkg->expire,
+ adjourn => $cust_pkg->adjourn,
+ contract_end => $cust_pkg->contract_end,
+ refnum => $cust_pkg->refnum,
+ discountnum => $cust_pkg->discountnum,
+ waive_setup => $cust_pkg->waive_setup,
+ });
+ if ( $old and $opt->{'keep_dates'} ) {
+ foreach (qw(setup bill last_bill)) {
+ $new->set($_, $old->get($_));
+ }
}
+ $error = $new->insert( allow_pkgpart => $same_pkgpart );
+ # transfer services
+ if ( $old ) {
+ $error ||= $old->transfer($new);
+ }
+ if ( $error and $error > 0 ) {
+ # no reason why this should ever fail, but still...
+ $error = "Unable to transfer all services from supplemental package ".
+ $old->pkgnum;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ push @new_supp_pkgs, $new;
}
- $error = $new->insert( allow_pkgpart => $same_pkgpart );
- # transfer services
- if ( $old ) {
- $error ||= $old->transfer($new);
- }
- if ( $error and $error > 0 ) {
- # no reason why this should ever fail, but still...
- $error = "Unable to transfer all services from supplemental package ".
- $old->pkgnum;
- }
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- push @new_supp_pkgs, $new;
- }
+ } # if !$opt->{'cust_pkg'}
+ # because if there is one, then supplemental packages would already
+ # have been created for it.
#Good to go, cancel old package. Notify 'cancel' of whether to credit
#remaining time.
@@ -1994,6 +2044,11 @@ sub change {
#outstanding usage) if we are keeping dates (i.e. location changing),
#because the new package will be billed for the same date range.
#Supplemental packages are also canceled here.
+
+ # during scheduled changes, avoid canceling the package we just
+ # changed to (duh)
+ $self->set('change_to_pkgnum' => '');
+
$error = $self->cancel(
quiet => 1,
unused_credit => $unused_credit,
@@ -2022,6 +2077,144 @@ sub change {
}
+=item change_later OPTION => VALUE...
+
+Schedule a package change for a later date. This actually orders the new
+package immediately, but sets its start date for a future date, and sets
+the current package to expire on the same date.
+
+If the package is already scheduled for a change, this can be called with
+'start_date' to change the scheduled date, or with pkgpart and/or
+locationnum to modify the package change. To cancel the scheduled change
+entirely, see C<abort_change>.
+
+Options include:
+
+=over 4
+
+=item start_date
+
+The date for the package change. Required, and must be in the future.
+
+=item pkgpart
+
+=item locationnum
+
+The pkgpart and locationnum of the new package, with the same
+meaning as in C<change>.
+
+=back
+
+=cut
+
+sub change_later {
+ my $self = shift;
+ my $opt = ref($_[0]) ? shift : { @_ };
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $cust_main = $self->cust_main;
+
+ my $date = delete $opt->{'start_date'} or return 'start_date required';
+
+ if ( $date <= time ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "start_date $date is in the past";
+ }
+
+ my $error;
+
+ if ( $self->change_to_pkgnum ) {
+ my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum);
+ my $new_pkgpart = $opt->{'pkgpart'}
+ if $opt->{'pkgpart'} and $opt->{'pkgpart'} != $change_to->pkgpart;
+ my $new_locationnum = $opt->{'locationnum'}
+ if $opt->{'locationnum'} and $opt->{'locationnum'} != $change_to->locationnum;
+ if ( $new_pkgpart or $new_locationnum ) {
+ # it hasn't been billed yet, so in principle we could just edit
+ # it in place (w/o a package change), but that's bad form.
+ # So change the package according to the new options...
+ my $err_or_pkg = $change_to->change(%$opt);
+ if ( ref $err_or_pkg ) {
+ # Then set that package up for a future start.
+ $self->set('change_to_pkgnum', $err_or_pkg->pkgnum);
+ $self->set('expire', $date); # in case it's different
+ $err_or_pkg->set('start_date', $date);
+ $err_or_pkg->set('change_date', '');
+ $err_or_pkg->set('change_pkgnum', '');
+
+ $error = $self->replace ||
+ $err_or_pkg->replace ||
+ $change_to->cancel ||
+ $change_to->delete;
+ } else {
+ $error = $err_or_pkg;
+ }
+ } else { # change the start date only.
+ $self->set('expire', $date);
+ $change_to->set('start_date', $date);
+ $error = $self->replace || $change_to->replace;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ } else {
+ $dbh->commit if $oldAutoCommit;
+ return '';
+ }
+ } # if $self->change_to_pkgnum
+
+ my $new_pkgpart = $opt->{'pkgpart'}
+ if $opt->{'pkgpart'} and $opt->{'pkgpart'} != $self->pkgpart;
+ my $new_locationnum = $opt->{'locationnum'}
+ if $opt->{'locationnum'} and $opt->{'locationnum'} != $self->locationnum;
+ return '' unless $new_pkgpart or $new_locationnum; # wouldn't do anything
+
+ my %hash = (
+ 'custnum' => $self->custnum,
+ 'pkgpart' => ($opt->{'pkgpart'} || $self->pkgpart),
+ 'locationnum' => ($opt->{'locationnum'} || $self->locationnum),
+ 'start_date' => $date,
+ );
+ my $new = FS::cust_pkg->new(\%hash);
+ $error = $new->insert('change' => 1,
+ 'allow_pkgpart' => ($new_pkgpart ? 0 : 1));
+ if ( !$error ) {
+ $self->set('change_to_pkgnum', $new->pkgnum);
+ $self->set('expire', $date);
+ $error = $self->replace;
+ }
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ } else {
+ $dbh->commit if $oldAutoCommit;
+ }
+
+ $error;
+}
+
+=item abort_change
+
+Cancels a future package change scheduled by C<change_later>.
+
+=cut
+
+sub abort_change {
+ my $self = shift;
+ my $pkgnum = $self->change_to_pkgnum;
+ my $change_to = FS::cust_pkg->by_key($pkgnum) if $pkgnum;
+ my $error;
+ if ( $change_to ) {
+ $error = $change_to->cancel || $change_to->delete;
+ return $error if $error;
+ }
+ $self->set('change_to_pkgnum', '');
+ $self->set('expire', '');
+ $self->replace;
+}
+
=item set_quantity QUANTITY
Change the package's quantity field. This is the one package property
@@ -2485,11 +2678,13 @@ sub _sort_cust_svc {
my $sort =
sub ($$) { my ($a, $b) = @_; $b->[1] cmp $a->[1] or $a->[2] <=> $b->[2] };
+ my %pkg_svc = map { $_->svcpart => $_ }
+ qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+
map { $_->[0] }
sort $sort
map {
- my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
- 'svcpart' => $_->svcpart } );
+ my $pkg_svc = $pkg_svc{ $_->svcpart } || '';
[ $_,
$pkg_svc ? $pkg_svc->primary_svc : '',
$pkg_svc ? $pkg_svc->quantity : 0,
diff --git a/FS/FS/part_export/freeswitch.pm b/FS/FS/part_export/freeswitch.pm
index eb490fd85..ff0d243bb 100644
--- a/FS/FS/part_export/freeswitch.pm
+++ b/FS/FS/part_export/freeswitch.pm
@@ -27,6 +27,8 @@ tie my %options, 'Tie::IxHash',
<user id="<% $phonenum %>">
<params>
<param name="password" value="<% $sip_password %>"/>
+ <param name="nibble_account" value="<% $phonenum %>"/>
+ <param name="nibble_rate" value="<% $nibble_rate %>"/>
</params>
</user>
</domain>
diff --git a/FS/FS/part_export/freeswitch_multifile.pm b/FS/FS/part_export/freeswitch_multifile.pm
index 90a2b0469..7f79a0e68 100644
--- a/FS/FS/part_export/freeswitch_multifile.pm
+++ b/FS/FS/part_export/freeswitch_multifile.pm
@@ -26,6 +26,8 @@ tie my %options, 'Tie::IxHash',
<user id="<% $phonenum %>">
<params>
<param name="password" value="<% $sip_password %>"/>
+ <param name="nibble_account" value="<% $phonenum %>"/>
+ <param name="nibble_rate" value="<% $nibble_rate %>"/>
</params>
</user>
</domain>
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 22e8828d6..0722647b4 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -5,7 +5,7 @@ use strict;
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 );
@@ -116,6 +116,8 @@ If this record is not obsolete, will be null.
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
@@ -682,6 +684,7 @@ sub check {
)
|| $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
@@ -1072,9 +1075,39 @@ sub is_free {
}
}
+# 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 can_currency_exchange { 0; }
diff --git a/FS/FS/part_pkg/delayed_Mixin.pm b/FS/FS/part_pkg/delayed_Mixin.pm
index ab53bda06..ae286d351 100644
--- a/FS/FS/part_pkg/delayed_Mixin.pm
+++ b/FS/FS/part_pkg/delayed_Mixin.pm
@@ -2,6 +2,7 @@ package FS::part_pkg::delayed_Mixin;
use strict;
use vars qw(%info);
+use Time::Local qw(timelocal);
use NEXT;
%info = (
@@ -52,4 +53,15 @@ sub calc_remain {
sub can_start_date { ! shift->option('delay_setup', 1) }
+sub default_start_date {
+ my $self = shift;
+ if ( $self->option('delay_setup') and $self->option('free_days') ) {
+ my $delay = $self->option('free_days');
+
+ my ($mday, $mon, $year) = (localtime(time))[3,4,5];
+ return timelocal(0,0,0,$mday,$mon,$year) + 86400 * $self->option('free_days');
+ }
+ return $self->NEXT::default_start_date(@_);
+}
+
1;
diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm
index 2a048a115..3a069149f 100644
--- a/FS/FS/pay_batch.pm
+++ b/FS/FS/pay_batch.pm
@@ -946,7 +946,7 @@ sub export_batch {
my $info = $export_info{$format} or die "Format not found: '$format'\n";
- &{$info->{'init'}}($conf) if exists($info->{'init'});
+ &{$info->{'init'}}($conf, $self->agentnum) if exists($info->{'init'});
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
diff --git a/FS/FS/pay_batch/eft_canada.pm b/FS/FS/pay_batch/eft_canada.pm
index b24c9c3a4..64fd2f971 100644
--- a/FS/FS/pay_batch/eft_canada.pm
+++ b/FS/FS/pay_batch/eft_canada.pm
@@ -58,7 +58,13 @@ my %holiday = (
init => sub {
my $conf = shift;
- my @config = $conf->config('batchconfig-eft_canada');
+ my $agentnum = shift;
+ my @config;
+ if ( $conf->exists('batch-spoolagent') ) {
+ @config = $conf->config('batchconfig-eft_canada', $agentnum);
+ } else {
+ @config = $conf->config('batchconfig-eft_canada');
+ }
# SFTP login, password, trans code, delay time
my $process_delay;
($trans_code, $process_delay) = @config[2,3];