From b03bd63dcee4fce35d86e906b0379acdb6c76c27 Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Fri, 31 Jul 2015 01:33:11 -0500 Subject: [PATCH] RT#18361 Delay package from billing until services are provisioned [v3 backport] --- FS/FS/Schema.pm | 1 + FS/FS/cust_pkg.pm | 33 +++++++++++-------- FS/FS/cust_svc.pm | 63 ++++++++++++++++++++++++++++++++++++ FS/FS/part_pkg.pm | 21 +++++++++--- FS/FS/pkg_svc.pm | 3 ++ httemplate/edit/process/part_pkg.cgi | 5 ++- httemplate/elements/tr-pkg_svc.html | 13 ++++++++ 7 files changed, 119 insertions(+), 20 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 3d7248f33..e06fce65c 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2405,6 +2405,7 @@ sub tables_hashref { 'quantity', 'int', '', '', '', '', 'primary_svc','char', 'NULL', 1, '', '', 'hidden', 'char', 'NULL', 1, '', '', + 'provision_hold', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'pkgsvcnum', 'unique' => [ ['pkgpart', 'svcpart'] ], diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 0b5a07f7a..1d660ee02 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -3308,28 +3308,33 @@ Returns a list of FS::part_svc objects representing services included in this package but not yet provisioned. Each FS::part_svc object also has an extra field, I, which specifies the number of available services. +Accepts option I; if true, only returns part_svc for which the +associated pkg_svc has the provision_hold flag set. + =cut sub available_part_svc { my $self = shift; + my %opt = @_; my $pkg_quantity = $self->quantity || 1; grep { $_->num_avail > 0 } - map { - my $part_svc = $_->part_svc; - $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking - $pkg_quantity * $_->quantity - $self->num_cust_svc($_->svcpart); - - # more evil encapsulation breakage - if($part_svc->{'Hash'}{'num_avail'} > 0) { - my @exports = $part_svc->part_export_did; - $part_svc->{'Hash'}{'can_get_dids'} = scalar(@exports); - } - - $part_svc; - } - $self->part_pkg->pkg_svc; + map { + my $part_svc = $_->part_svc; + $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking + $pkg_quantity * $_->quantity - $self->num_cust_svc($_->svcpart); + + # more evil encapsulation breakage + if ($part_svc->{'Hash'}{'num_avail'} > 0) { + my @exports = $part_svc->part_export_did; + $part_svc->{'Hash'}{'can_get_dids'} = scalar(@exports); + } + + $part_svc; + } + grep { $opt{'provision_hold'} ? $_->provision_hold : 1 } + $self->part_pkg->pkg_svc; } =item part_svc [ OPTION => VALUE ... ] diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 12e2c788e..a1df059c5 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -102,6 +102,37 @@ sub table { 'cust_svc'; } Adds this service to the database. If there is an error, returns the error, otherwise returns false. +=cut + +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = $self->SUPER::insert; + + #check if this releases a hold (see FS::pkg_svc provision_hold) + $error ||= $self->_provision_hold; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error if $error + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; #no error + +} + =item delete Deletes this service from the database. If there is an error, returns the @@ -360,6 +391,9 @@ sub replace { } # if ($svc_x->locationnum) } # if this is a location change + #check if this releases a hold (see FS::pkg_svc provision_hold) + $error ||= $new->_provision_hold; + if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error if $error @@ -1073,6 +1107,35 @@ sub smart_search_param { ); } +# If the associated cust_pkg is 'on hold' +# and the associated pkg_svc has the provision_hold flag +# and there are no more available_part_svcs on the cust_pkg similarly flagged, +# then removes hold from pkg +# returns $error or '' on success, +# does not indicate if pkg status was changed +sub _provision_hold { + my $self = shift; + + # check status of cust_pkg + my $cust_pkg = $self->cust_pkg; + return '' unless $cust_pkg->status eq 'on hold'; + + # check flag on this svc + # small false laziness with $self->pkg_svc + # to avoid looking up cust_pkg twice + my $pkg_svc = qsearchs( 'pkg_svc', { + 'svcpart' => $self->svcpart, + 'pkgpart' => $cust_pkg->pkgpart, + }); + return '' unless $pkg_svc->provision_hold; + + # check for any others available with that flag + return '' if $cust_pkg->available_part_svc( 'provision_hold' => 1 ); + + # conditions met, remove hold + return $cust_pkg->unsuspend; +} + sub _upgrade_data { my $class = shift; diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 18a065da1..402d88cb9 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -168,7 +168,8 @@ I and I. If I is set to a hashref with svcparts as keys and quantities as values, appropriate FS::pkg_svc records will be inserted. I 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 can be set similarly +for the 'provision_hold' field in these records. If I is set to the svcpart of the primary service, the appropriate FS::pkg_svc record will be updated. @@ -248,6 +249,7 @@ sub insert { 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 = @@ -261,6 +263,7 @@ sub insert { '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 ) { @@ -335,13 +338,15 @@ sub delete { 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, I, I -and I +Currently available options are: I, I, I, +I, I and I If I is set to a hashref with svcparts as keys and quantities as values, the appropriate FS::pkg_svc records will be replaced. I 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. I can be set +to a hashref of svcparts and flag values ('Y' or '') to set the field +in those records. If I is set to the svcpart of the primary service, the appropriate FS::pkg_svc record will be updated. @@ -447,10 +452,12 @@ sub replace { warn " replacing pkg_svc records" if $DEBUG; my $pkg_svc = $options->{'pkg_svc'}; my $hidden_svc = $options->{'hidden_svc'} || {}; + 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 $provision_hold = $provision_hold->{$part_svc->svcpart} || ''; my $primary_svc = ( defined($options->{'primary_svc'}) && $options->{'primary_svc'} && $options->{'primary_svc'} == $part_svc->svcpart @@ -466,16 +473,19 @@ sub replace { my $old_quantity = 0; my $old_primary_svc = ''; my $old_hidden = ''; + 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_provision_hold = $old_pkg_svc->provision_hold; } next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc || - $old_hidden ne $hidden; + $old_hidden ne $hidden || + $old_provision_hold ne $provision_hold; my $new_pkg_svc = new FS::pkg_svc( { 'pkgsvcnum' => ( $old_pkg_svc ? $old_pkg_svc->pkgsvcnum : '' ), @@ -484,6 +494,7 @@ sub replace { 'quantity' => $quantity, 'primary_svc' => $primary_svc, 'hidden' => $hidden, + 'provision_hold' => $provision_hold, } ); my $error = $old_pkg_svc ? $new_pkg_svc->replace($old_pkg_svc) diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm index f79bb5e2d..fa82ec05b 100644 --- a/FS/FS/pkg_svc.pm +++ b/FS/FS/pkg_svc.pm @@ -52,6 +52,8 @@ definition includes =item hidden - 'Y' to hide this service on invoices, null otherwise. +=item provision_hold - 'Y' to release package hold when all services marked with this are provisioned + =back =head1 METHODS @@ -112,6 +114,7 @@ sub check { || $self->ut_number('svcpart') || $self->ut_number('quantity') || $self->ut_enum('hidden', [ '', 'Y' ] ) + || $self->ut_flag('provision_hold') ; return $error if $error; diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 8e8be853d..3ffd5fc23 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -128,8 +128,11 @@ my $args_callback = sub { my @svcparts = map { $_->svcpart } qsearch('part_svc', {}); my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) } @svcparts; my %hidden_svc = map { $_ => scalar($cgi->param("hidden$_")) } @svcparts; + my %provision_hold = map { $_ => scalar($cgi->param("provision_hold$_" )) } @svcparts; - push @args, 'pkg_svc' => \%pkg_svc, 'hidden_svc' => \%hidden_svc; + push @args, 'pkg_svc' => \%pkg_svc, + 'hidden_svc' => \%hidden_svc, + 'provision_hold' => \%provision_hold; ### # cust_pkg and custnum_ref (inserts only) diff --git a/httemplate/elements/tr-pkg_svc.html b/httemplate/elements/tr-pkg_svc.html index 8acbca118..b3bf80212 100644 --- a/httemplate/elements/tr-pkg_svc.html +++ b/httemplate/elements/tr-pkg_svc.html @@ -32,6 +32,13 @@ % $quan = $pkg_svc->quantity; % } % +% my $provision_hold = ''; +% if ( grep { $_ eq "provision_hold$svcpart" } $cgi->param ) { +% $provision_hold = $cgi->param("hidden_svc$svcpart"); +% } else { +% $provision_hold = $pkg_svc->provision_hold; +% } +% % my @exports = $pkg_svc->part_svc->part_export; % foreach my $export ( @exports ) { % push @possible_exports, $export if $export->can('external_pkg_map'); @@ -53,6 +60,11 @@ hidden =~ /^Y/i ? ' CHECKED' : ''%>> + + + > + + % foreach ( 1 .. $columns-1 ) { % if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) { @@ -106,6 +118,7 @@ my $thead = "\n\n". ntable('#cccccc', 2). 'Primary'. 'Service'. 'Hide'. + 'Hold
Until
Provision
'. ''; my $part_pkg = $opt{'object'}; -- 2.11.0