From 08e5a2a3a80b1dd98d71025c3a8665252a609036 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Tue, 30 Jul 2013 18:35:47 +0200 Subject: [PATCH] Propagate reseller and contract status to children Properly terminate/lock/unlock sub-contracts and subscribers if the parent contract status changes. --- lib/NGCP/Panel/Controller/Contract.pm | 45 +++++++---------- lib/NGCP/Panel/Controller/Reseller.pm | 57 +++++++++++++++------ lib/NGCP/Panel/Utils/Contract.pm | 71 +++++++++++++++++++++++++++ lib/NGCP/Panel/Utils/Subscriber.pm | 30 +++++++++++ share/templates/contract/list.tt | 1 - share/templates/customer/list.tt | 2 +- share/templates/reseller/details.tt | 2 +- share/templates/reseller/list.tt | 10 ++-- 8 files changed, 170 insertions(+), 48 deletions(-) diff --git a/lib/NGCP/Panel/Controller/Contract.pm b/lib/NGCP/Panel/Controller/Contract.pm index 0750b0d0cf..7670875dc0 100644 --- a/lib/NGCP/Panel/Controller/Contract.pm +++ b/lib/NGCP/Panel/Controller/Contract.pm @@ -6,6 +6,7 @@ use Hash::Merge; use NGCP::Panel::Form::Contract; use NGCP::Panel::Utils::Navigation; use NGCP::Panel::Utils::Contract; +use NGCP::Panel::Utils::Subscriber; sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) { my ($self, $c) = @_; @@ -208,21 +209,20 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) { billing_profile_id => $form->values->{billing_profile}{id}, }); } + my $old_status = $contract->status; delete $form->values->{billing_profile}; $form->values->{contact_id} = $form->values->{contact}{id}; delete $form->values->{contact}; $contract->update($form->values); - # terminate all voip subscribers if contract is terminated - if($contract->status eq "terminated") { - for my $subscriber($contract->voip_subscribers->all) { - $subscriber->update({ status => 'terminated' }); - $subscriber->provisioning_voip_subscriber->delete; - } + # if status changed, populate it down the chain + if($contract->status ne $old_status) { + NGCP::Panel::Utils::Contract::recursively_lock_contract( + c => $c, + contract => $contract, + ); } - # TODO: what about terminating a peering contract? - delete $c->session->{created_objects}->{contact}; delete $c->session->{created_objects}->{billing_profile}; }); @@ -243,7 +243,16 @@ sub terminate :Chained('base') :PathPart('terminate') :Args(0) { my ($self, $c) = @_; try { - $c->stash->{contract_result}->update({ status => 'terminated' }); + my $contract = $c->stash->{contract_result}; + my $old_status = $contract->status; + $contract->update({ status => 'terminated' }); + # if status changed, populate it down the chain + if($contract->status ne $old_status) { + NGCP::Panel::Utils::Contract::recursively_lock_contract( + c => $c, + contract => $contract, + ); + } $c->flash(messages => [{type => 'success', text => 'Contract successfully terminated'}]); } catch (DBIx::Class::Exception $e) { $c->log->info("failed to terminate contract: $e"); @@ -252,29 +261,11 @@ sub terminate :Chained('base') :PathPart('terminate') :Args(0) { NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for); } -sub delete :Chained('base') :PathPart('delete') :Args(0) { - my ($self, $c) = @_; - - try { - $c->stash->{contract_result}->delete; - $c->flash(messages => [{type => 'success', text => 'Contract successfully deleted'}]); - } catch (DBIx::Class::Exception $e) { - $c->log->info("failed to delete contract: $e"); - $c->flash(messages => [{type => 'error', text => 'Failed to delete contract'}]); - }; - NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for); -} - sub ajax :Chained('contract_list') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; my $res = $c->stash->{contract_select_rs}; NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{contract_dt_columns}); - -# $c->forward( "/ajax_process_resultset", [$rs, -# ["id", "contact_id", "billing_profile_name", "billing_profile_id", "status"], -# ["billing_profile.name", "status"]]); - $c->detach( $c->view("JSON") ); } diff --git a/lib/NGCP/Panel/Controller/Reseller.pm b/lib/NGCP/Panel/Controller/Reseller.pm index 3e7175ae3b..d75b50133b 100644 --- a/lib/NGCP/Panel/Controller/Reseller.pm +++ b/lib/NGCP/Panel/Controller/Reseller.pm @@ -7,6 +7,7 @@ use Hash::Merge; use HTTP::Status qw(HTTP_SEE_OTHER); use NGCP::Panel::Form::Reseller; use NGCP::Panel::Utils::Navigation; +use NGCP::Panel::Utils::Contract; sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) { my ($self, $c) = @_; @@ -210,21 +211,21 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) { if($posted && $form->validated) { try { - $form->params->{contract_id} = delete $form->params->{contract}{id}; - delete $form->params->{contract}; - $c->stash->{reseller}->first->update($form->params); - - # if a reseller is terminated, we need to terminate all customers - # and subscribers - if($c->stash->{reseller}->first->status eq "terminated") { - for my $customer($c->stash->{reseller}->first->contracts->all) { - $customer->update({ status => 'terminated' }); - for my $subscriber($customer->voip_subscribers->all) { - $subscriber->update({ status => 'terminated' }); - $subscriber->provisioning_voip_subscriber->delete; - } + $c->model('DB')->txn_do(sub { + $form->params->{contract_id} = delete $form->params->{contract}{id}; + delete $form->params->{contract}; + my $old_status = $c->stash->{reseller}->first->status; + $c->stash->{reseller}->first->update($form->params); + + if($c->stash->{reseller}->first->status ne $old_status) { + my $contract = $c->stash->{reseller}->first->contract; + $contract->update({ status => $c->stash->{reseller}->first->status }); + NGCP::Panel::Utils::Contract::recursively_lock_contract( + c => $c, + contract => $contract, + ); } - } + }); delete $c->session->{created_objects}->{contract}; delete $c->session->{edit_contract_id}; @@ -233,7 +234,7 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) { $c->log->error($e); $c->flash(messages => [{type => 'error', text => 'Failed to update reseller'}]); } - $c->response->redirect($c->uri_for()); + NGCP::Utils::Navigation::back_or($c, $c->uri_for); } $c->stash(close_target => $c->uri_for()); @@ -243,6 +244,32 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) { return; } +sub terminate :Chained('base') :PathPart('terminate') :Args(0) { + my ($self, $c) = @_; + + try { + $c->model('DB')->txn_do(sub { + my $reseller = $c->stash->{reseller}->first; + my $old_status = $reseller->status; + $reseller->update({ status => 'terminated' }); + + if($reseller->status ne $old_status) { + my $contract = $reseller->contract; + $contract->update({ status => $c->stash->{reseller}->first->status }); + NGCP::Panel::Utils::Contract::recursively_lock_contract( + c => $c, + contract => $contract, + ); + } + }); + $c->flash(messages => [{type => 'success', text => 'Successfully terminated reseller'}]); + } catch($e) { + $c->log->error("failed to terminate reseller: $e"); + $c->flash(messages => [{type => 'error', text => 'Failed to terminate reseller'}]); + } + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for); +} + sub details :Chained('base') :PathPart('details') :Args(0) { my ($self, $c) = @_; $c->stash(template => 'reseller/details.tt'); diff --git a/lib/NGCP/Panel/Utils/Contract.pm b/lib/NGCP/Panel/Utils/Contract.pm index 7b60946f64..be7be14a0a 100644 --- a/lib/NGCP/Panel/Utils/Contract.pm +++ b/lib/NGCP/Panel/Utils/Contract.pm @@ -55,6 +55,77 @@ sub create_contract_balance { } } +sub recursively_lock_contract { + my %params = @_; + + my $c = $params{c}; + my $contract = $params{contract}; + my $status = $contract->status; + + # first, change all voip subscribers, in case there are any + for my $subscriber($contract->voip_subscribers->all) { + $subscriber->update({ status => $status }); + if($status eq 'terminated') { + $subscriber->provisioning_voip_subscriber->delete; + } elsif($status eq 'locked') { + NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber( + c => $c, + prov_subscriber => $subscriber->provisioning_voip_subscriber, + level => 4, + ); + } elsif($status eq 'active') { + NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber( + c => $c, + prov_subscriber => $subscriber->provisioning_voip_subscriber, + level => 0, + ); + } + } + + # then, check all child contracts in case of reseller + my $resellers = $c->model('DB')->resultset('resellers')->search({ + contract_id => $contract->id + }); + for my $reseller($resellers->all) { + + # remove domains in case of reseller termination + if($status eq 'terminated') { + for my $domain($reseller->domain_resellers->all) { + $domain->delete; + } + } + + # fetch sub-contracts of this contract + my $customers = $c->model('DB')->resultset('contracts')->search({ + 'contact.reseller_id' => $reseller->id, + }, { + join => 'contact', + } + ); + for my $customer($customers->all) { + $customer->update({ status => $status }); + for my $subscriber($customer->voip_subscribers->all) { + $subscriber->update({ status => $status }); + if($status eq 'terminated') { + $subscriber->provisioning_voip_subscriber->delete; + } elsif($status eq 'locked') { + NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber( + c => $c, + prov_subscriber => $subscriber->provisioning_voip_subscriber, + level => 4, + ); + } elsif($status eq 'active') { + NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber( + c => $c, + prov_subscriber => $subscriber->provisioning_voip_subscriber, + level => 0, + ); + } + } + } + } +} + 1; =head1 NAME diff --git a/lib/NGCP/Panel/Utils/Subscriber.pm b/lib/NGCP/Panel/Utils/Subscriber.pm index 9305dfe111..8d6fd3fd85 100644 --- a/lib/NGCP/Panel/Utils/Subscriber.pm +++ b/lib/NGCP/Panel/Utils/Subscriber.pm @@ -79,6 +79,36 @@ sub destination_as_string { } } +sub lock_provisoning_voip_subscriber { + my %params = @_; + + my $c = $params{c}; + my $prov_subscriber= $params{prov_subscriber}; + my $level = $params{level}; + + return unless $prov_subscriber; + + my $rs = get_usr_preference_rs( + c => $c, + prov_subscriber => $prov_subscriber, + attribute => 'lock' + ); + try { + if($rs->first) { + if($level == 0) { + $rs->first->delete; + } else { + $rs->first->update({ value => $level }); + } + } elsif($level > 0) { # nothing to do for level 0, if no lock is set yet + $rs->create({ value => $level }); + } + } catch($e) { + $c->log->error("failed to set provisioning_voip_subscriber lock: $e"); + $e->rethrow; + } +} + 1; =head1 NAME diff --git a/share/templates/contract/list.tt b/share/templates/contract/list.tt index 350fb19345..fdab7eaf92 100644 --- a/share/templates/contract/list.tt +++ b/share/templates/contract/list.tt @@ -15,7 +15,6 @@ helper.dt_buttons = [ { name = 'Edit', uri = "/contract/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, { name = 'Terminate', uri = "/contract/'+full.id+'/terminate", class = 'btn-small btn-primary', icon = 'icon-remove' }, - { name = 'Delete', uri = "/contract/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, ]; helper.top_buttons = [ { name = 'Create Contract', uri = c.uri_for('/contract/create'), icon = 'icon-star' }, diff --git a/share/templates/customer/list.tt b/share/templates/customer/list.tt index 2fd1622efd..242fce27bc 100644 --- a/share/templates/customer/list.tt +++ b/share/templates/customer/list.tt @@ -14,7 +14,7 @@ UNLESS c.user.read_only; helper.dt_buttons = [ { name = 'Edit', uri = "/contract/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, - { name = 'Delete', uri = "/contract/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, + { name = 'Terminate', uri = "/contract/'+full.id+'/terminate", class = 'btn-small btn-secondary', icon = 'icon-remove' }, { name = 'Details', uri = "/customer/'+full.id+'/details", class = 'btn-small btn-tertiary', icon = 'icon-list' }, ]; helper.top_buttons = [ diff --git a/share/templates/reseller/details.tt b/share/templates/reseller/details.tt index f91add303d..cadec19621 100644 --- a/share/templates/reseller/details.tt +++ b/share/templates/reseller/details.tt @@ -134,7 +134,7 @@ UNLESS c.user.read_only; helper.dt_buttons = [ { name = 'Edit', uri = "/contract/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, - { name = 'Delete', uri = "/contract/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, + { name = 'Terminate', uri = "/contract/'+full.id+'/terminate", class = 'btn-small btn-secondary', icon = 'icon-remove' }, { name = 'Details', uri = "/customer/'+full.id+'/details", class = 'btn-small btn-tertiary', icon = 'icon-list' }, ]; ELSE; diff --git a/share/templates/reseller/list.tt b/share/templates/reseller/list.tt index a463d4eb48..49a1ed13c1 100644 --- a/share/templates/reseller/list.tt +++ b/share/templates/reseller/list.tt @@ -13,14 +13,18 @@ UNLESS c.user.read_only; helper.dt_buttons = [ - { name = 'Edit', uri = "/reseller/'+full[\"id\"]+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, - { name = 'Delete', uri = "/reseller/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, - { name = 'Details', uri = "/reseller/'+full[\"id\"]+'/details", class = 'btn-small btn-tertiary', icon = 'icon-list' }, + { name = 'Edit', uri = "/reseller/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, + { name = 'Terminate', uri = "/reseller/'+full.id+'/terminate", class = 'btn-small btn-secondary', icon = 'icon-remove' }, + { name = 'Details', uri = "/reseller/'+full.id+'/details", class = 'btn-small btn-tertiary', icon = 'icon-list' }, ]; helper.top_buttons = [ { name = 'Create Reseller', uri = c.uri_for('/reseller/create'), icon = 'icon-star' }, { name = 'Create Reseller with default values', uri = c.uri_for('/reseller/create_defaults'), icon = 'icon-star', method = 'post' }, ]; + ELSE; + helper.dt_buttons = [ + { name = 'Details', uri = "/reseller/'+full.id+'/details", class = 'btn-small btn-tertiary', icon = 'icon-list' }, + ]; END; PROCESS 'helpers/datatables.tt';