MT#33041 add reseller max subscribers support

* Max subscribers can now be defined in the Reseller contract
* Max subscribers is now shown in int Reseller's "Reseller Contract"
  Details (with an indictaion of how many subscribers are created
  out of the defined max subscribers, as well as when the limit
  is reached (similar to the Customer's Details page)
* Add reseller subscribers count checks on the UI Create Subscriber
  page
* Add reseller subscribers count checks in /api/contracts POST/PUT
* Prevent max_subscribers definition for Peering contracts in
  /api/contracts POST/PUT

Change-Id: I1561d4eb7da5b1a0a0c99acabd18d2a9cd98dec7
mr12.0
Kirill Solomko 2 years ago
parent 8fbd46e5fc
commit 6953de937c

@ -200,6 +200,12 @@ sub POST :Allow {
return; 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; my $now = NGCP::Panel::Utils::DateTime::current_local;
$resource->{create_timestamp} = $now; $resource->{create_timestamp} = $now;
$resource->{modify_timestamp} = $now; $resource->{modify_timestamp} = $now;

@ -131,11 +131,15 @@ sub edit :Chained('base') :PathPart('edit') :Args(0) {
$params->{contact}{id} = $contract->contact_id; $params->{contact}{id} = $contract->contact_id;
$params->{external_id} = $contract->external_id; $params->{external_id} = $contract->external_id;
$params->{status} = $contract->status; $params->{status} = $contract->status;
$params->{max_subscribers} = $contract->max_subscribers;
} }
$params = merge($params, $c->session->{created_objects}); $params = merge($params, $c->session->{created_objects});
my ($form, $is_peering_reseller); my ($form, $is_peering_reseller);
if ( NGCP::Panel::Utils::Contract::is_peering_reseller_contract( c => $c, contract => $contract ) ) { if ( NGCP::Panel::Utils::Contract::is_peering_product( c => $c, product => $contract->product ) ) {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::PeeringReseller", $c); $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; $is_peering_reseller = 1;
} else { } else {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Contract", $c); $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->{type} = 'sippeering';
$c->stash(ajax_uri => $c->uri_for_action("/contract/ajax")); $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( $form->process(
posted => $posted, posted => $posted,
params => $c->request->params, 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'), { 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' ) }, 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 => "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); NGCP::Panel::Utils::Datatables::process($c, $rs, $contract_columns);
$c->detach( $c->view("JSON") ); $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->{type} = 'reseller';
$c->stash(ajax_uri => $c->uri_for_action("/contract/ajax")); $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( $form->process(
posted => $posted, posted => $posted,
params => $c->request->params, params => $c->request->params,

@ -19,6 +19,7 @@ use NGCP::Panel::Utils::Voucher;
use NGCP::Panel::Utils::ContractLocations qw(); use NGCP::Panel::Utils::ContractLocations qw();
use NGCP::Panel::Utils::Events qw(); use NGCP::Panel::Utils::Events qw();
use NGCP::Panel::Utils::Phonebook; use NGCP::Panel::Utils::Phonebook;
use NGCP::Panel::Utils::Reseller;
use Template; use Template;
=head1 NAME =head1 NAME
@ -744,15 +745,32 @@ sub details :Chained('base') :PathPart('details') :Args(0) {
sub subscriber_create :Chained('base') :PathPart('subscriber/create') :Args(0) { sub subscriber_create :Chained('base') :PathPart('subscriber/create') :Args(0) {
my ($self, $c) = @_; my ($self, $c) = @_;
if(defined $c->stash->{contract}->max_subscribers && if (defined $c->stash->{contract}->max_subscribers &&
$c->stash->{contract}->voip_subscribers $c->stash->{contract}->voip_subscribers
->search({ status => { -not_in => ['terminated'] } }) ->search({ status => { -not_in => ['terminated'] } })
->count >= $c->stash->{contract}->max_subscribers) { ->count >= $c->stash->{contract}->max_subscribers) {
NGCP::Panel::Utils::Message::error( NGCP::Panel::Utils::Message::error(
c => $c, c => $c,
error => "tried to exceed max number of subscribers of " . $c->stash->{contract}->max_subscribers, error => "tried to exceed max number of customer subscribers: " . $c->stash->{contract}->max_subscribers,
desc => $c->loc('Maximum number of subscribers for this customer reached'), 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, NGCP::Panel::Utils::Navigation::back_or($c,
$c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) $c->uri_for_action('/customer/details', [$c->stash->{contract}->id])

@ -34,8 +34,11 @@ sub list_reseller :Chained('/') :PathPart('reseller') :CaptureArgs(0) {
$c->stash( $c->stash(
resellers => $c->model('DB') resellers => $c->model('DB')
->resultset('resellers')->search({ ->resultset('resellers')->search({
status => { '!=' => 'terminated' } 'me.status' => { '!=' => 'terminated' }
},{
join => 'contract',
}), }),
template => 'reseller/list.tt' 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'), { 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' ) }, 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 => "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') $c->detach('/denied_page')
if($c->user->roles eq "reseller" && $c->user->reseller_id != $reseller_id); 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) { unless($reseller->first) {
NGCP::Panel::Utils::Message::error( NGCP::Panel::Utils::Message::error(
c => $c, c => $c,
@ -233,6 +237,12 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) {
$c->stash->{timesets_rs} = $reseller->first->time_sets; $c->stash->{timesets_rs} = $reseller->first->time_sets;
$c->stash->{branding} = $reseller->first->branding; $c->stash->{branding} = $reseller->first->branding;
$c->stash->{phonebook} = $reseller->first->phonebook; $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) { sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {

@ -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; 1;

@ -1,4 +1,4 @@
package NGCP::Panel::Form::Contract::PeeringReseller; package NGCP::Panel::Form::Contract::Peering;
use HTML::FormHandler::Moose; use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::Contract::Base'; extends 'NGCP::Panel::Form::Contract::Base';

@ -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:

@ -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."); $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Peering/reseller contract can't be connected to the prepaid billing profile $prepaid_billing_profile_exist.");
return; 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 $resource->{modify_timestamp} = $now; #problematic for ON UPDATE current_timestamp columns
if($old_resource->{contact_id} != $resource->{contact_id}) { if($old_resource->{contact_id} != $resource->{contact_id}) {

@ -432,6 +432,26 @@ sub is_peering_reseller_product {
return 0; 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 { sub acquire_contract_rowlocks {
my %params = @_; 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/}; my($c,$schema,$rs,$contract_id_field,$contract_ids,$contract_id) = @params{qw/c schema rs contract_id_field contract_ids contract_id/};

@ -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; 1;

@ -380,7 +380,18 @@ sub prepare_resource {
status => { '!=' => 'terminated' }, status => { '!=' => 'terminated' },
})->count >= $customer->max_subscribers) { })->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; return;
} }

@ -62,6 +62,15 @@
</div> </div>
<div class="accordion-body collapse" id="collapse_contract"> <div class="accordion-body collapse" id="collapse_contract">
<div class="accordion-inner"> <div class="accordion-inner">
[% IF reseller.first.contract.max_subscribers.defined && subscriber_count < reseller.first.contract.max_subscribers -%]
<div class="alert alert-info">
[% c.loc('[_1] of maximum [_2] subscribers (including PBX groups) created', subscriber_count, reseller.first.contract.max_subscribers) %]
</div>
[% ELSIF reseller.first.contract.max_subscribers.defined -%]
<div class="alert">
[% c.loc('Maximum number of [_1] subscribers (including PBX groups) created', reseller.first.contract.max_subscribers) %]
</div>
[% END -%]
[% [%
helper.name = c.loc('Contract'); helper.name = c.loc('Contract');
helper.identifier = "Contract"; helper.identifier = "Contract";

Loading…
Cancel
Save