diff --git a/lib/NGCP/Panel/Controller/API/Contracts.pm b/lib/NGCP/Panel/Controller/API/Contracts.pm index 2074419c5e..462f7e65f0 100644 --- a/lib/NGCP/Panel/Controller/API/Contracts.pm +++ b/lib/NGCP/Panel/Controller/API/Contracts.pm @@ -200,6 +200,12 @@ sub POST :Allow { return; } + if (NGCP::Panel::Utils::Contract::is_peering_product( + c => $c, product => $product) && defined $resource->{max_subscribers}) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Peering contract should not have 'max_subscribers' defined."); + return; + } + my $now = NGCP::Panel::Utils::DateTime::current_local; $resource->{create_timestamp} = $now; $resource->{modify_timestamp} = $now; diff --git a/lib/NGCP/Panel/Controller/Contract.pm b/lib/NGCP/Panel/Controller/Contract.pm index 93cfe7a38e..a64f37bf48 100644 --- a/lib/NGCP/Panel/Controller/Contract.pm +++ b/lib/NGCP/Panel/Controller/Contract.pm @@ -131,11 +131,15 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) { $params->{contact}{id} = $contract->contact_id; $params->{external_id} = $contract->external_id; $params->{status} = $contract->status; + $params->{max_subscribers} = $contract->max_subscribers; } $params = merge($params, $c->session->{created_objects}); my ($form, $is_peering_reseller); - if ( NGCP::Panel::Utils::Contract::is_peering_reseller_contract( c => $c, contract => $contract ) ) { - $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::PeeringReseller", $c); + if ( NGCP::Panel::Utils::Contract::is_peering_product( c => $c, product => $contract->product ) ) { + $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Peering", $c); + $is_peering_reseller = 1; + } elsif ( NGCP::Panel::Utils::Contract::is_reseller_product( c => $c, product => $contract->product ) ) { + $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Reseller", $c); $is_peering_reseller = 1; } else { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Contract", $c); @@ -355,7 +359,7 @@ sub peering_create :Chained('peering_list') :PathPart('create') :Args(0) { } $c->stash->{type} = 'sippeering'; $c->stash(ajax_uri => $c->uri_for_action("/contract/ajax")); - my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::PeeringReseller", $c); + my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Peering", $c); $form->process( posted => $posted, params => $c->request->params, @@ -480,6 +484,7 @@ sub reseller_ajax_contract_filter :Chained('reseller_list') :PathPart('ajax/cont { name => 'billing_profile_name', accessor => "billing_profile_name", search => 0, title => $c->loc('Billing Profile'), literal_sql => NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping_stmt(c => $c, now => $now, projection => 'billing_profile.name' ) }, { name => "status", search => 1, title => $c->loc("Status") }, + { name => "max_subscribers", search => 1, title => $c->loc("Max. Subscribers") }, ]); NGCP::Panel::Utils::Datatables::process($c, $rs, $contract_columns); $c->detach( $c->view("JSON") ); @@ -496,7 +501,7 @@ sub reseller_create :Chained('reseller_list') :PathPart('create') :Args(0) { } $c->stash->{type} = 'reseller'; $c->stash(ajax_uri => $c->uri_for_action("/contract/ajax")); - my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::PeeringReseller", $c); + my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Reseller", $c); $form->process( posted => $posted, params => $c->request->params, diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index b0062fb02e..323fca3e74 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -19,6 +19,7 @@ use NGCP::Panel::Utils::Voucher; use NGCP::Panel::Utils::ContractLocations qw(); use NGCP::Panel::Utils::Events qw(); use NGCP::Panel::Utils::Phonebook; +use NGCP::Panel::Utils::Reseller; use Template; =head1 NAME @@ -744,15 +745,32 @@ sub details :Chained('base') :PathPart('details') :Args(0) { sub subscriber_create :Chained('base') :PathPart('subscriber/create') :Args(0) { my ($self, $c) = @_; - if(defined $c->stash->{contract}->max_subscribers && + if (defined $c->stash->{contract}->max_subscribers && $c->stash->{contract}->voip_subscribers ->search({ status => { -not_in => ['terminated'] } }) ->count >= $c->stash->{contract}->max_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, - error => "tried to exceed max number of subscribers of " . $c->stash->{contract}->max_subscribers, - desc => $c->loc('Maximum number of subscribers for this customer reached'), + error => "tried to exceed max number of customer subscribers: " . $c->stash->{contract}->max_subscribers, + desc => $c->loc('Maximum number of subscribers for this customer is reached'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, + $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) + ); + } + + my $reseller = $c->stash->{contract}->contact->reseller; + my $reseller_max_subscribers = $reseller->contract->max_subscribers; + if (defined $reseller_max_subscribers && + NGCP::Panel::Utils::Reseller::get_subscribers_count( + $c, $reseller + ) >= $reseller_max_subscribers) { + + NGCP::Panel::Utils::Message::error( + c => $c, + error => "tried to exceed max number of reseller subscribers: " . $reseller_max_subscribers, + desc => $c->loc('Maximum number of subscribers for this reseller is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) diff --git a/lib/NGCP/Panel/Controller/Reseller.pm b/lib/NGCP/Panel/Controller/Reseller.pm index 8767271aeb..cd69fc30e0 100644 --- a/lib/NGCP/Panel/Controller/Reseller.pm +++ b/lib/NGCP/Panel/Controller/Reseller.pm @@ -34,8 +34,11 @@ sub list_reseller :Chained('/') :PathPart('reseller') :CaptureArgs(0) { $c->stash( resellers => $c->model('DB') ->resultset('resellers')->search({ - status => { '!=' => 'terminated' } + 'me.status' => { '!=' => 'terminated' } + },{ + join => 'contract', }), + template => 'reseller/list.tt' ); @@ -54,6 +57,7 @@ sub list_reseller :Chained('/') :PathPart('reseller') :CaptureArgs(0) { { name => 'billing_profile_name', accessor => "billing_profile_name", search => 0, title => $c->loc('Billing Profile'), literal_sql => NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping_stmt(c => $c, projection => 'billing_profile.name' ) }, { name => "status", search => 1, title => $c->loc("Status") }, + { name => "max_subscribers", search => 1, title => $c->loc("Max. Subscribers") }, ]); } @@ -147,7 +151,7 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) { $c->detach('/denied_page') if($c->user->roles eq "reseller" && $c->user->reseller_id != $reseller_id); - my $reseller = $c->stash->{resellers}->search_rs({ id => $reseller_id }); + my $reseller = $c->stash->{resellers}->search_rs({ 'me.id' => $reseller_id }); unless($reseller->first) { NGCP::Panel::Utils::Message::error( c => $c, @@ -233,6 +237,12 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) { $c->stash->{timesets_rs} = $reseller->first->time_sets; $c->stash->{branding} = $reseller->first->branding; $c->stash->{phonebook} = $reseller->first->phonebook; + + if (defined $reseller->first->contract->max_subscribers) { + $c->stash->{subscriber_count} = NGCP::Panel::Utils::Reseller::get_subscribers_count( + $c, $reseller->first + ); + } } sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) { diff --git a/lib/NGCP/Panel/Form/Contract/ContractAPI.pm b/lib/NGCP/Panel/Form/Contract/ContractAPI.pm index 7781b08912..8f39b8a249 100644 --- a/lib/NGCP/Panel/Form/Contract/ContractAPI.pm +++ b/lib/NGCP/Panel/Form/Contract/ContractAPI.pm @@ -42,4 +42,14 @@ has_field 'type' => ( }, ); +has_field 'max_subscribers' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Optionally set the maximum number of subscribers for this contract (reseller only). Leave empty for unlimited.'] + }, +); + + 1; diff --git a/lib/NGCP/Panel/Form/Contract/PeeringReseller.pm b/lib/NGCP/Panel/Form/Contract/Peering.pm similarity index 95% rename from lib/NGCP/Panel/Form/Contract/PeeringReseller.pm rename to lib/NGCP/Panel/Form/Contract/Peering.pm index efa9cd5219..877eb4d922 100644 --- a/lib/NGCP/Panel/Form/Contract/PeeringReseller.pm +++ b/lib/NGCP/Panel/Form/Contract/Peering.pm @@ -1,4 +1,4 @@ -package NGCP::Panel::Form::Contract::PeeringReseller; +package NGCP::Panel::Form::Contract::Peering; use HTML::FormHandler::Moose; extends 'NGCP::Panel::Form::Contract::Base'; diff --git a/lib/NGCP/Panel/Form/Contract/Reseller.pm b/lib/NGCP/Panel/Form/Contract/Reseller.pm new file mode 100644 index 0000000000..500a21da5e --- /dev/null +++ b/lib/NGCP/Panel/Form/Contract/Reseller.pm @@ -0,0 +1,52 @@ +package NGCP::Panel::Form::Contract::Reseller; + +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::Contract::Base'; + +has '+prepaid_billing_profile_forbidden' => ( default => 1 ); + +has_field 'contact' => ( + type => '+NGCP::Panel::Field::ContactNoReseller', + label => 'Contact', + validate_when_empty => 1, + element_attr => { + rel => ['tooltip'], + title => ['The contact id this contract belongs to.'] + }, +); + +has_field 'billing_profile' => ( + type => '+NGCP::Panel::Field::BillingProfileNoPrepaid', + element_attr => { + rel => ['tooltip'], + title => ['The billing profile used to charge this contract.'] + }, +); + +has_field 'billing_profiles.profile' => ( + type => '+NGCP::Panel::Field::BillingProfileNoPrepaid', + validate_when_empty => 1, + element_attr => { + rel => ['tooltip'], + title => ['The billing profile used to charge this contract.'] + }, +); + +has_field 'max_subscribers' => ( + type => 'PosInteger', + label => 'Max Subscribers', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Optionally set the maximum number of subscribers for this reseller contract. Leave empty for unlimited.'] + }, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/contact billing_profile_definition billing_profile billing_profiles profile_add status external_id max_subscribers/], +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Contracts.pm b/lib/NGCP/Panel/Role/API/Contracts.pm index 5ec8130a87..04514f7c18 100644 --- a/lib/NGCP/Panel/Role/API/Contracts.pm +++ b/lib/NGCP/Panel/Role/API/Contracts.pm @@ -168,6 +168,12 @@ sub update_contract { $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Peering/reseller contract can't be connected to the prepaid billing profile $prepaid_billing_profile_exist."); return; } + + if (NGCP::Panel::Utils::Contract::is_peering_product( + c => $c, product => $contract->product) && defined $resource->{max_subscribers}) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Peering contract should not have 'max_subscribers' defined."); + return; + } $resource->{modify_timestamp} = $now; #problematic for ON UPDATE current_timestamp columns if($old_resource->{contact_id} != $resource->{contact_id}) { diff --git a/lib/NGCP/Panel/Utils/Contract.pm b/lib/NGCP/Panel/Utils/Contract.pm index 0455e166e4..ec0cb6099f 100644 --- a/lib/NGCP/Panel/Utils/Contract.pm +++ b/lib/NGCP/Panel/Utils/Contract.pm @@ -432,6 +432,26 @@ sub is_peering_reseller_product { return 0; } +sub is_peering_product { + my %params = @_; + my($c, $product) = @params{qw/c product/}; + if (grep {$product->handle eq $_} + ("SIP_PEERING", "PSTN_PEERING")) { + return 1; + } + return 0; +} + +sub is_reseller_product { + my %params = @_; + my($c, $product) = @params{qw/c product/}; + if (grep {$product->handle eq $_} + ("VOIP_RESELLER")) { + return 1; + } + return 0; +} + sub acquire_contract_rowlocks { my %params = @_; my($c,$schema,$rs,$contract_id_field,$contract_ids,$contract_id) = @params{qw/c schema rs contract_id_field contract_ids contract_id/}; diff --git a/lib/NGCP/Panel/Utils/Reseller.pm b/lib/NGCP/Panel/Utils/Reseller.pm index 8228c9a982..082765cb51 100644 --- a/lib/NGCP/Panel/Utils/Reseller.pm +++ b/lib/NGCP/Panel/Utils/Reseller.pm @@ -157,6 +157,18 @@ sub _handle_reseller_status_change { } } +sub get_subscribers_count { + my ($c, $reseller) = @_; + + my $count = $reseller->search_related('contacts', { + 'voip_subscribers.status' => { -not_in => ['terminated'] } + },{ + join => { contracts => 'voip_subscribers' }, + })->count; + + return $count; +} + 1; diff --git a/lib/NGCP/Panel/Utils/Subscriber.pm b/lib/NGCP/Panel/Utils/Subscriber.pm index 184e9604b6..a7c2e53fd8 100644 --- a/lib/NGCP/Panel/Utils/Subscriber.pm +++ b/lib/NGCP/Panel/Utils/Subscriber.pm @@ -380,7 +380,18 @@ sub prepare_resource { status => { '!=' => 'terminated' }, })->count >= $customer->max_subscribers) { - &{$err_code}(HTTP_FORBIDDEN, "Maximum number of subscribers reached."); + &{$err_code}(HTTP_FORBIDDEN, "Maximum number of customer subscribers reached."); + return; + } + + my $reseller = $customer->contact->reseller; + my $reseller_max_subscribers = $reseller->contract->max_subscribers; + if (!$item && defined $reseller_max_subscribers && + NGCP::Panel::Utils::Reseller::get_subscribers_count( + $c, $reseller + ) >= $reseller_max_subscribers) { + + &{$err_code}(HTTP_FORBIDDEN, "Maximum number of reseller subscribers reached."); return; } diff --git a/share/templates/reseller/details.tt b/share/templates/reseller/details.tt index 19abfa8c41..8e55cbd164 100644 --- a/share/templates/reseller/details.tt +++ b/share/templates/reseller/details.tt @@ -62,6 +62,15 @@
+ [% IF reseller.first.contract.max_subscribers.defined && subscriber_count < reseller.first.contract.max_subscribers -%] +
+ [% c.loc('[_1] of maximum [_2] subscribers (including PBX groups) created', subscriber_count, reseller.first.contract.max_subscribers) %] +
+ [% ELSIF reseller.first.contract.max_subscribers.defined -%] +
+ [% c.loc('Maximum number of [_1] subscribers (including PBX groups) created', reseller.first.contract.max_subscribers) %] +
+ [% END -%] [% helper.name = c.loc('Contract'); helper.identifier = "Contract";