summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2014-09-16 01:44:26 -0700
committerMark Wells <mark@freeside.biz>2014-09-16 14:55:02 -0700
commit9d96031e79187a549c2c150e96363d421e59efc5 (patch)
tree655c1c4d97d0e4e21b3a7368ab78b1211844cee0
parent32834b6e8a92ca3ec0bbf58075956a7d0edca79a (diff)
package start_on_hold flag, and better behavior for automatic timers + packages on hold, #25853
-rw-r--r--FS/FS/Schema.pm1
-rw-r--r--FS/FS/cust_pkg.pm80
-rw-r--r--FS/FS/part_pkg.pm24
-rw-r--r--FS/FS/part_pkg/flat.pm1
-rwxr-xr-xhttemplate/edit/part_pkg.cgi5
-rw-r--r--httemplate/elements/order_pkg.js24
-rw-r--r--httemplate/elements/tr-select-cust-part_pkg.html17
-rw-r--r--httemplate/misc/cust-part_pkg.cgi1
8 files changed, 105 insertions, 48 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 3eedc5cc3..3c12f0f9f 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2150,6 +2150,7 @@ sub tables_hashref {
'successor', 'int', 'NULL', '', '', '',
'family_pkgpart','int', 'NULL', '', '', '',
'delay_start', 'int', 'NULL', '', '', '',
+ 'start_on_hold', 'char', 'NULL', 1, '', '',
'agent_pkgpartid', 'varchar', 'NULL', 20, '', '',
],
'primary_key' => 'pkgpart',
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 059384911..e0b0eaca7 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -243,6 +243,39 @@ sub cust_unlinked_msg {
' (cust_pkg.pkgnum '. $self->pkgnum. ')';
}
+=item set_initial_timers
+
+If required by the package definition, sets any automatic expire, adjourn,
+or contract_end timers to some number of months after the start date
+(or setup date, if the package has already been setup). If the package has
+a delayed setup fee after a period of "free days", will also set the
+start date to the end of that period.
+
+=cut
+
+sub set_initial_timers {
+ my $self = shift;
+ my $part_pkg = $self->part_pkg;
+ foreach my $action ( qw(expire adjourn contract_end) ) {
+ my $months = $part_pkg->option("${action}_months",1);
+ if($months and !$self->get($action)) {
+ my $start = $self->start_date || $self->setup || time;
+ $self->set($action, $part_pkg->add_freq($start, $months) );
+ }
+ }
+
+ # if this package has "free days" and delayed setup fee, then
+ # set start date that many days in the future.
+ # (this should have been set in the UI, but enforce it here)
+ if ( $part_pkg->option('free_days',1)
+ && $part_pkg->option('delay_setup',1)
+ )
+ {
+ $self->start_date( $part_pkg->default_start_date );
+ }
+ '';
+}
+
=item insert [ OPTION => VALUE ... ]
Adds this billing item to the database ("Orders" the item). If there is an
@@ -301,6 +334,9 @@ sub insert {
if ( ! $options{'change'} ) {
+ # set order date to now
+ $self->order_date(time);
+
# 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];
@@ -309,32 +345,17 @@ 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) {
- my $start = $self->start_date || $self->setup || time;
- $self->$action( $part_pkg->add_freq($start, $months) );
- }
- }
-
- # if this package has "free days" and delayed setup fee, then
- # set start date that many days in the future.
- # (this should have been set in the UI, but enforce it here)
- if ( ! $options{'change'}
- && $part_pkg->option('free_days',1)
- && $part_pkg->option('delay_setup',1)
- #&& ! $self->start_date
- )
- {
- $self->start_date( $part_pkg->default_start_date );
+ if ($self->susp eq 'now' or $part_pkg->start_on_hold) {
+ # if the package was ordered on hold:
+ # - suspend it
+ # - don't set the start date (it will be started manually)
+ $self->set('susp', $self->order_date);
+ $self->set('start_date', '');
+ } else {
+ # set expire/adjourn/contract_end timers, and free days, if appropriate
+ $self->set_initial_timers;
}
-
- }
-
- # set order date unless this was previously a different package
- $self->order_date(time) unless $self->change_pkgnum;
+ } # else this is a package change, and shouldn't have "new package" behavior
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
@@ -343,8 +364,6 @@ sub insert {
local $SIG{TSTP} = 'IGNORE';
local $SIG{PIPE} = 'IGNORE';
- $self->susp( $self->order_date ) if $self->susp eq 'now';
-
my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
@@ -1510,6 +1529,8 @@ sub unsuspend {
return ""; # no error # complain instead?
}
+ # handle the case of setting a future unsuspend (resume) date
+ # and do not continue to actually unsuspend the package
my $date = $opt{'date'};
if ( $date and $date > time ) { # return an error if $date <= time?
@@ -1533,6 +1554,11 @@ sub unsuspend {
} #if $date
+ if (!$self->setup) {
+ # then this package is being released from on-hold status
+ $self->set_initial_timers;
+ }
+
my @labels = ();
foreach my $cust_svc (
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index c6e4c3950..b7b9be51b 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -120,6 +120,10 @@ part_pkg, will be equal to pkgpart.
=item delay_start - Number of days to delay package start, by default
+=item start_on_hold - 'Y' to suspend this package immediately when it is
+ordered. The package will not start billing or have a setup fee charged
+until it is manually unsuspended.
+
=back
=head1 METHODS
@@ -601,14 +605,15 @@ sub check {
|| $self->ut_textn('comment')
|| $self->ut_textn('promo_code')
|| $self->ut_alphan('plan')
- || $self->ut_enum('setuptax', [ '', 'Y' ] )
- || $self->ut_enum('recurtax', [ '', 'Y' ] )
+ || $self->ut_flag('setuptax')
+ || $self->ut_flag('recurtax')
|| $self->ut_textn('taxclass')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
- || $self->ut_enum('custom', [ '', 'Y' ] )
- || $self->ut_enum('no_auto', [ '', 'Y' ])
- || $self->ut_enum('recur_show_zero', [ '', 'Y' ])
- || $self->ut_enum('setup_show_zero', [ '', 'Y' ])
+ || $self->ut_flag('disabled')
+ || $self->ut_flag('custom')
+ || $self->ut_flag('no_auto')
+ || $self->ut_flag('recur_show_zero')
+ || $self->ut_flag('setup_show_zero')
+ || $self->ut_flag('start_on_hold')
#|| $self->ut_moneyn('setup_cost')
#|| $self->ut_moneyn('recur_cost')
|| $self->ut_floatn('setup_cost')
@@ -1082,7 +1087,10 @@ sub is_free {
sub can_discount { 0; }
# whether the plan allows changing the start date
-sub can_start_date { 1; }
+sub can_start_date {
+ my $self = shift;
+ $self->start_on_hold ? 0 : 1;
+}
# the delay start date if present
sub delay_start_date {
diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm
index f3a2b85a5..5fd269642 100644
--- a/FS/FS/part_pkg/flat.pm
+++ b/FS/FS/part_pkg/flat.pm
@@ -256,6 +256,7 @@ sub is_prepaid { 0; } #no, we're postpaid
sub can_start_date {
my $self = shift;
my %opt = @_;
+ return 0 if $self->start_on_hold;
! $self->option('start_1st', 1) && ( ! $self->option('sync_bill_date',1)
|| ! $self->option('prorate_defer_bill',1)
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index a007a9255..f40d4a9e3 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -43,6 +43,7 @@
'plan' => 'Price plan',
'disabled' => 'Disable new orders',
'disable_line_item_date_ranges' => 'Disable line item date ranges',
+ 'start_on_hold' => 'Start on hold',
'setup_cost' => 'Setup cost',
'recur_cost' => 'Recur cost',
'pay_weight' => 'Payment weight',
@@ -102,6 +103,10 @@
),
{field=>'disabled', type=>$disabled_type, value=>'Y'},
{field=>'disable_line_item_date_ranges', type=>$disabled_type, value=>'Y'},
+ { field => 'start_on_hold',
+ type => 'checkbox',
+ value => 'Y'
+ },
{ type => 'tablebreak-tr-title',
value => 'Pricing', #better name?
diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js
index 8cd0f5f2a..d7a909187 100644
--- a/httemplate/elements/order_pkg.js
+++ b/httemplate/elements/order_pkg.js
@@ -10,7 +10,7 @@ function pkg_changed () {
var date_text = document.getElementById('start_date_text');
var radio_now = document.getElementById('start_now');
- //var radio_on_hold = document.getElementById('start_on_hold');
+ var radio_on_hold = document.getElementById('start_on_hold');
var radio_on_date = document.getElementById('start_on_date');
form.submitButton.disabled = false;
@@ -36,23 +36,35 @@ function pkg_changed () {
date_button.style.display = '';
date_button_disabled.style.display = 'none';
if ( radio_on_date ) {
+ // un-disable all the buttons that might get disabled
radio_on_date.disabled = false;
- if ( form.start_date_text.value.length > 0 && radio_now.checked ) {
+ radio_now.disabled = false;
+ // if a start date has been entered, assume the user wants it
+ if ( form.start_date_text.value.length > 0 ) {
radio_now.checked = false;
radio_on_date.checked = true;
+ } else {
+ // if not, default to now
+ radio_now.checked = true;
}
}
- } else {
+ } else { // the package is either fixed start date or start-on-hold
date_text.style.backgroundColor = '#dddddd';
date_text.disabled = true;
date_button.style.display = 'none';
date_button_disabled.style.display = '';
if ( radio_on_date ) {
- if ( radio_on_date.checked ) {
- radio_on_date.checked = false;
+ if ( opt.getAttribute('data-start_on_hold') == 1 ) {
+ // disallow all options but "On hold"
+ radio_on_hold.checked = true;
+ radio_now.checked = false;
+ radio_now.disabled = true;
+ } else {
+ // disallow all options but "On date"
+ radio_on_hold.checked = false;
radio_now.checked = true;
+ radio_now.disabled = false;
}
- radio_on_date.disabled = true;
}
}
diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html
index 696baff9f..0db989aed 100644
--- a/httemplate/elements/tr-select-cust-part_pkg.html
+++ b/httemplate/elements/tr-select-cust-part_pkg.html
@@ -5,9 +5,10 @@
<SCRIPT TYPE="text/javascript">
- function part_pkg_opt(what, value, text, can_discount, can_start_date, start_date) {
+ function part_pkg_opt(what, value, text, can_discount, start_on_hold, can_start_date, start_date) {
var optionName = new Option(text, value, false, false);
optionName.setAttribute('data-can_discount', can_discount);
+ optionName.setAttribute('data-start_on_hold', start_on_hold);
optionName.setAttribute('data-can_start_date', can_start_date);
optionName.setAttribute('data-start_date', start_date || '');
var length = what.length;
@@ -37,14 +38,16 @@
// add the new packages
opt(what.form.pkgpart, '', 'Select package');
var packagesArray = eval('(' + part_pkg + ')' );
- for ( var s = 0; s < packagesArray.length; s=s+5 ) {
+ while (packagesArray.length > 0) {
//surely this should be some kind of JSON structure
- var packagesLabel = packagesArray[s+1];
- var can_discount = packagesArray[s+2];
- var can_start_date = packagesArray[s+3];
- var start_date = packagesArray[s+4];
+ var pkgpart = packagesArray.shift();
+ var label = packagesArray.shift();
+ var can_discount = packagesArray.shift();
+ var start_on_hold = packagesArray.shift();
+ var can_start_date = packagesArray.shift();
+ var start_date = packagesArray.shift();
part_pkg_opt(
- what.form.pkgpart, packagesArray[s], packagesLabel, can_discount, can_start_date, start_date
+ what.form.pkgpart, pkgpart, label, can_discount, start_on_hold, can_start_date, start_date
);
}
diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi
index e129347ec..dc9ba2af1 100644
--- a/httemplate/misc/cust-part_pkg.cgi
+++ b/httemplate/misc/cust-part_pkg.cgi
@@ -56,6 +56,7 @@ my @return = map {
( $_->pkgpart,
$_->pkg_comment,
$_->can_discount,
+ ($_->start_on_hold ? 1 : 0),
$_->can_start_date(
num_ncancelled_pkgs => $num_ncancelled_pkgs,
),