diff --git a/lib/NGCP/Panel/Controller/API/Calls.pm b/lib/NGCP/Panel/Controller/API/Calls.pm
new file mode 100644
index 0000000000..3fb24dd025
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/Calls.pm
@@ -0,0 +1,173 @@
+package NGCP::Panel::Controller::API::Calls;
+use Sipwise::Base;
+use namespace::sweep;
+use boolean qw(true);
+use Data::HAL qw();
+use Data::HAL::Link qw();
+use HTTP::Headers qw();
+use HTTP::Status qw(:constants);
+use MooseX::ClassAttribute qw(class_has);
+use NGCP::Panel::Utils::DateTime;
+use Path::Tiny qw(path);
+use Safe::Isa qw($_isa);
+BEGIN { extends 'Catalyst::Controller::ActionRole'; }
+require Catalyst::ActionRole::ACL;
+require Catalyst::ActionRole::CheckTrailingSlash;
+require Catalyst::ActionRole::HTTPMethods;
+require Catalyst::ActionRole::RequireSSL;
+
+class_has 'api_description' => (
+ is => 'ro',
+ isa => 'Str',
+ default =>
+ 'Defines calls placed or received by a customer.',
+);
+
+class_has 'query_params' => (
+ is => 'ro',
+ isa => 'ArrayRef',
+ default => sub {[
+ {
+ param => 'customer_id',
+ description => 'Filter for calls of a specific customer',
+ query => {
+ first => sub {
+ my $q = shift;
+ {
+ -or => [
+ { source_account_id => $q },
+ { destination_account_id => $q },
+ ],
+ };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'subscriber_id',
+ description => 'Filter for calls of a specific subscriber',
+ query => {
+ first => sub {
+ my $q = shift;
+ return {
+ -or => [
+ { 'source_subscriber.id' => $q },
+ { 'destination_subscriber.id' => $q }
+ ],
+ };
+ },
+ second => sub {
+ return {
+ join => ['source_subscriber', 'destination_subscriber'],
+ };
+ },
+ },
+ },
+ ]},
+);
+
+with 'NGCP::Panel::Role::API::Calls';
+
+class_has('resource_name', is => 'ro', default => 'calls');
+class_has('dispatch_path', is => 'ro', default => '/api/calls/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-calls');
+
+__PACKAGE__->config(
+ action => {
+ map { $_ => {
+ ACLDetachTo => '/api/root/invalid_user',
+ AllowedRole => [qw/admin reseller/],
+ Args => 0,
+ Does => [qw(ACL CheckTrailingSlash RequireSSL)],
+ Method => $_,
+ Path => __PACKAGE__->dispatch_path,
+ } } @{ __PACKAGE__->allowed_methods }
+ },
+ action_roles => [qw(HTTPMethods)],
+);
+
+sub auto :Private {
+ my ($self, $c) = @_;
+
+ $self->set_body($c);
+ $self->log_request($c);
+}
+
+sub GET :Allow {
+ my ($self, $c) = @_;
+ my $page = $c->request->params->{page} // 1;
+ my $rows = $c->request->params->{rows} // 10;
+ {
+ my $items = $self->item_rs($c);
+ my $total_count = int($items->count);
+ $items = $items->search(undef, {
+ page => $page,
+ rows => $rows,
+ });
+ my (@embedded, @links);
+ my $form = $self->get_form($c);
+ for my $item ($items->search({}, {order_by => {-asc => 'me.id'}})->all) {
+ push @embedded, $self->hal_from_item($c, $item, $form);
+ push @links, Data::HAL::Link->new(
+ relation => 'ngcp:'.$self->resource_name,
+ href => sprintf('/%s%d', $c->request->path, $item->id),
+ );
+ }
+ push @links,
+ Data::HAL::Link->new(
+ relation => 'curies',
+ href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
+ name => 'ngcp',
+ templated => true,
+ ),
+ Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
+ Data::HAL::Link->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows));
+ if(($total_count / $rows) > $page ) {
+ push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows));
+ }
+ if($page > 1) {
+ push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows));
+ }
+
+ my $hal = Data::HAL->new(
+ embedded => [@embedded],
+ links => [@links],
+ );
+ $hal->resource({
+ total_count => $total_count,
+ });
+ my $response = HTTP::Response->new(HTTP_OK, undef,
+ HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
+ $c->response->headers($response->headers);
+ $c->response->body($response->content);
+ return;
+ }
+ return;
+}
+
+sub HEAD :Allow {
+ my ($self, $c) = @_;
+ $c->forward(qw(GET));
+ $c->response->body(q());
+ return;
+}
+
+sub OPTIONS :Allow {
+ my ($self, $c) = @_;
+ my $allowed_methods = $self->allowed_methods;
+ $c->response->headers(HTTP::Headers->new(
+ Allow => $allowed_methods->join(', '),
+ Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name,
+ ));
+ $c->response->content_type('application/json');
+ $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
+ return;
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/CallsItem.pm b/lib/NGCP/Panel/Controller/API/CallsItem.pm
new file mode 100644
index 0000000000..a8b0793c43
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/CallsItem.pm
@@ -0,0 +1,91 @@
+package NGCP::Panel::Controller::API::CallsItem;
+use Sipwise::Base;
+use namespace::sweep;
+use HTTP::Headers qw();
+use HTTP::Status qw(:constants);
+use MooseX::ClassAttribute qw(class_has);
+use NGCP::Panel::Utils::DateTime;
+use NGCP::Panel::Utils::ValidateJSON qw();
+use Path::Tiny qw(path);
+use Safe::Isa qw($_isa);
+BEGIN { extends 'Catalyst::Controller::ActionRole'; }
+require Catalyst::ActionRole::ACL;
+require Catalyst::ActionRole::HTTPMethods;
+require Catalyst::ActionRole::RequireSSL;
+
+with 'NGCP::Panel::Role::API::Calls';
+
+class_has('resource_name', is => 'ro', default => 'calls');
+class_has('dispatch_path', is => 'ro', default => '/api/calls/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-calls');
+
+__PACKAGE__->config(
+ action => {
+ map { $_ => {
+ ACLDetachTo => '/api/root/invalid_user',
+ AllowedRole => [qw/admin reseller/],
+ Args => 1,
+ Does => [qw(ACL RequireSSL)],
+ Method => $_,
+ Path => __PACKAGE__->dispatch_path,
+ } } @{ __PACKAGE__->allowed_methods }
+ },
+ action_roles => [qw(HTTPMethods)],
+);
+
+sub auto :Private {
+ my ($self, $c) = @_;
+
+ $self->set_body($c);
+ $self->log_request($c);
+}
+
+sub GET :Allow {
+ my ($self, $c, $id) = @_;
+ {
+ last unless $self->valid_id($c, $id);
+ my $item = $self->item_by_id($c, $id);
+ last unless $self->resource_exists($c, call => $item);
+
+ my $hal = $self->hal_from_item($c, $item);
+
+ my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
+ (map { # XXX Data::HAL must be able to generate links with multiple relations
+ s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|;
+ s/rel=self/rel="item self"/;
+ $_
+ } $hal->http_headers),
+ ), $hal->as_json);
+ $c->response->headers($response->headers);
+ $c->response->body($response->content);
+ return;
+ }
+ return;
+}
+
+sub HEAD :Allow {
+ my ($self, $c, $id) = @_;
+ $c->forward(qw(GET));
+ $c->response->body(q());
+ return;
+}
+
+sub OPTIONS :Allow {
+ my ($self, $c, $id) = @_;
+ my $allowed_methods = $self->allowed_methods;
+ $c->response->headers(HTTP::Headers->new(
+ Allow => $allowed_methods->join(', '),
+ Accept_Patch => 'application/json-patch+json',
+ ));
+ $c->response->content_type('application/json');
+ $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
+ return;
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm
index 0484920ed3..8fe0feb670 100644
--- a/lib/NGCP/Panel/Controller/API/Root.pm
+++ b/lib/NGCP/Panel/Controller/API/Root.pm
@@ -67,7 +67,7 @@ sub GET : Allow {
$query_params = $full_mod->query_params;
}
my $actions = [ keys %{ $full_mod->config->{action} } ];
- my $item_actions = [ keys %{ $full_item_mod->config->{action} } ];
+ my $item_actions = $full_item_mod->can('config') ? [ keys %{ $full_item_mod->config->{action} } ] : [];
my $form = $full_mod->get_form($c);
diff --git a/lib/NGCP/Panel/Form/Call/Admin.pm b/lib/NGCP/Panel/Form/Call/Admin.pm
new file mode 100644
index 0000000000..717c941518
--- /dev/null
+++ b/lib/NGCP/Panel/Form/Call/Admin.pm
@@ -0,0 +1,149 @@
+package NGCP::Panel::Form::Call::Admin;
+
+use HTML::FormHandler::Moose;
+extends 'NGCP::Panel::Form::Call::Reseller';
+use Moose::Util::TypeConstraints;
+
+has_field 'source_carrier_cost' => (
+ type => 'Float',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the operator towards the peering carrier.']
+ },
+);
+
+has_field 'source_carrier_free_time' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the carrier contract for this call.']
+ },
+);
+
+has_field 'source_carrier_billing_fee_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the source carrier cost.']
+ },
+);
+
+has_field 'source_reseller_billing_fee_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the source reseller cost.']
+ },
+);
+
+has_field 'source_carrier_billing_zone_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The source billing zone id (from billing.billing_zones_history) attached to the carrier billing cost.']
+ },
+);
+
+has_field 'source_reseller_billing_zone_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The source billing zone id (from billing.billing_zones_history) attached to the reseller billing cost.']
+ },
+);
+
+has_field 'destination_carrier_cost' => (
+ type => 'Float',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the carrier towards the operator (e.g. for 800-numbers).']
+ },
+);
+
+has_field 'destination_carrier_free_time' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the carrier contract for this call.']
+ },
+);
+
+has_field 'destination_carrier_billing_fee_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the destination carrier cost.']
+ },
+);
+
+has_field 'destination_reseller_billing_fee_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the destination reseller cost.']
+ },
+);
+
+has_field 'destination_carrier_billing_zone_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The destination billing zone id (from billing.billing_zones_history) attached to the carrier billing cost.']
+ },
+);
+
+has_field 'destination_reseller_billing_zone_id' => (
+ type => 'PosInteger',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The destination billing zone id (from billing.billing_zones_history) attached to the reseller billing cost.']
+ },
+);
+
+has_field 'rated_at' => (
+ type => 'Text',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The timestamp when the rating occured.']
+ },
+ required => 0,
+);
+
+has_field 'rating_status' => (
+ type => 'Select',
+ options => [
+ { label => 'unrated', 'value' => 'unrated' },
+ { label => 'ok', 'value' => 'ok' },
+ { label => 'failed', 'value' => 'failed' },
+ ],
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The status of the rating process.']
+ },
+);
+
+has_field 'exported_at' => (
+ type => 'Text',
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The timestamp when the exporting occured.']
+ },
+ required => 0,
+);
+
+has_field 'export_status' => (
+ type => 'Select',
+ options => [
+ { label => 'unexported', 'value' => 'unexported' },
+ { label => 'ok', 'value' => 'ok' },
+ { label => 'failed', 'value' => 'failed' },
+ ],
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The status of the exporting process.']
+ },
+);
+
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Form/Call/Reseller.pm b/lib/NGCP/Panel/Form/Call/Reseller.pm
new file mode 100644
index 0000000000..bad8aba479
--- /dev/null
+++ b/lib/NGCP/Panel/Form/Call/Reseller.pm
@@ -0,0 +1,406 @@
+package NGCP::Panel::Form::Call::Reseller;
+
+use HTML::FormHandler::Moose;
+extends 'HTML::FormHandler';
+use Moose::Util::TypeConstraints;
+
+use HTML::FormHandler::Widget::Block::Bootstrap;
+
+has '+widget_wrapper' => ( default => 'Bootstrap' );
+has_field 'submitid' => ( type => 'Hidden' );
+
+has_field 'source_user_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['UUID of calling subscriber, or 0 if from external.']
+ },
+);
+
+has_field 'source_provider_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Reseller contract id of calling subscriber, or contract id of peer if from external.']
+ },
+);
+
+has_field 'source_external_subscriber_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['External ID of calling subscriber, if local.']
+ },
+);
+
+has_field 'source_external_contract_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['External ID of the calling subscriber\'s customer, if local.']
+ },
+);
+
+has_field 'source_customer_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Customer id of calling subscriber, if local.']
+ },
+);
+
+has_field 'source_user' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Username of calling party.']
+ },
+);
+
+has_field 'source_domain' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Domain of calling party.']
+ },
+);
+
+has_field 'source_cli' => (
+ type => 'Text',
+ label => 'Source CLI',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Normalized CLI (usually E164) of calling party.']
+ },
+);
+
+has_field 'source_clir' => (
+ type => 'Boolean',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Whether calling party number was suppressed (CLIR).']
+ },
+);
+
+has_field 'source_ip' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['IP address of calling party.']
+ },
+);
+
+has_field 'destination_user_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['UUID of called subscriber, or 0 if to external.']
+ },
+);
+
+has_field 'destination_provider_id' => (
+ type => 'Text',
+ label => 'Reseller id of called subscriber, or contract id of peer if to external',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Reseller contract id of called subscriber, or contract id of peer if to external.']
+ },
+);
+
+has_field 'destination_external_subscriber_id' => (
+ type => 'Text',
+ label => 'external_id of called subscriber',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['External id of called subscriber, if local.']
+ },
+);
+
+has_field 'destination_external_contract_id' => (
+ type => 'Text',
+ label => 'external_id of called subscriber',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['External id of called subscriber\'s customer, if local.']
+ },
+);
+
+has_field 'destination_customer_id' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Customer id of called subscriber, if local.']
+ },
+);
+
+has_field 'destination_user' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Username or number of called party.']
+ },
+);
+
+has_field 'destination_domain' => (
+ type => 'Text',
+ label => 'Destination Domain',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Domain of called party.']
+ },
+);
+
+has_field 'destination_user_dialed' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Destination username or number as received by the system from calling party before any internal rewriting.']
+ },
+);
+
+has_field 'destination_user_in' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Destination username or number as received by the system from calling party after internal rewriting.']
+ },
+);
+
+has_field 'destination_domain_in' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['Destination domain as received by the system from calling party after internal rewriting.']
+ },
+);
+
+has_field 'peer_auth_user' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The authentication username used for outbound authentication.']
+ },
+);
+
+has_field 'peer_auth_realm' => (
+ type => 'Text',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The authentication realm (domain) used for outbound authentication.']
+ },
+);
+
+has_field 'call_type' => (
+ type => 'Select',
+ required => 1,
+ options => [
+ { label => 'call', value => 'call' },
+ { label => 'cfu', value => 'cfu' },
+ { label => 'cfb', value => 'cfb' },
+ { label => 'cft', value => 'cft' },
+ { label => 'cfna', value => 'cfna' },
+ ],
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The type of call, one of call, cfu, cfb, cft, cfna.']
+ },
+);
+
+has_field 'call_status' => (
+ type => 'Select',
+ required => 1,
+ options => [
+ { label => 'ok', value => 'ok' },
+ { label => 'busy', value => 'busy' },
+ { label => 'noanswer', value => 'noanswer' },
+ { label => 'cancel', value => 'cancel' },
+ { label => 'offline', value => 'offline' },
+ { label => 'timeout', value => 'timeout' },
+ { label => 'other', value => 'other' },
+ ],
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The status of the call, one of ok, busy, noanswer, cancel, offline, timeout, other.']
+ },
+);
+
+has_field 'call_code' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The final SIP response code of the call.']
+ },
+);
+
+has_field 'init_time' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The timestamp of the call initiation.']
+ },
+);
+
+has_field 'start_time' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The timestamp of the call connection.']
+ },
+);
+
+has_field 'duration' => (
+ type => 'PosInteger',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The duration of the call.']
+ },
+);
+
+has_field 'call_id' => (
+ type => 'Text',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The value of the SIP Call-ID header for this call.']
+ },
+);
+
+has_field 'source_reseller_cost' => (
+ type => 'Float',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the reseller of the calling party towards the system operator.']
+ },
+);
+
+has_field 'source_customer_cost' => (
+ type => 'Float',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the calling party customer towards the reseller.']
+ },
+);
+
+has_field 'source_reseller_free_time' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the reseller used for this call.']
+ },
+);
+
+has_field 'source_customer_free_time' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the customer used for this call.']
+ },
+);
+
+has_field 'source_customer_billing_fee_id' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the source customer cost.']
+ },
+);
+
+has_field 'source_customer_billing_zone_id' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The source billing zone id (from billing.billing_zones_history) attached to the customer billing cost.']
+ },
+);
+
+has_field 'destination_reseller_cost' => (
+ type => 'Float',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the reseller of the called party towards the system operator.']
+ },
+);
+
+has_field 'destination_customer_cost' => (
+ type => 'Float',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The cost for the called party customer towards the reseller.']
+ },
+);
+
+has_field 'destination_reseller_free_time' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the reseller used for this call.']
+ },
+);
+
+has_field 'destination_customer_free_time' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The number of free seconds of the customer used for this call.']
+ },
+);
+
+has_field 'destination_customer_billing_fee_id' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The billing fee id used to calculate the destination customer cost.']
+ },
+);
+
+has_field 'destination_customer_billing_zone_id' => (
+ type => 'PosInteger',
+ required => 0,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The destination billing zone id (from billing.billing_zones_history) attached to the customer billing cost.']
+ },
+);
+
+
+
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm
index 4c78ef3381..0779d4892b 100644
--- a/lib/NGCP/Panel/Role/API.pm
+++ b/lib/NGCP/Panel/Role/API.pm
@@ -76,12 +76,15 @@ sub validate_form {
my $resource = $params{resource};
my $form = $params{form};
my $run = $params{run} // 1;
+ my $exceptions = $params{exceptions} // [];
+ push @{ $exceptions }, "external_id";
my @normalized = ();
# move {xxx_id} into {xxx}{id} for FormHandler
foreach my $key(keys %{ $resource } ) {
- if($key =~ /^(.+)_id$/ && $key ne "external_id") {
+ my $skip_normalize = grep {/^$key$/} @{ $exceptions };
+ if($key =~ /^(.+)_id$/ && !$skip_normalize && !exists $resource->{$1}) {
push @normalized, $1;
$resource->{$1}{id} = delete $resource->{$key};
}
diff --git a/lib/NGCP/Panel/Role/API/Calls.pm b/lib/NGCP/Panel/Role/API/Calls.pm
new file mode 100644
index 0000000000..7ab6843a80
--- /dev/null
+++ b/lib/NGCP/Panel/Role/API/Calls.pm
@@ -0,0 +1,133 @@
+package NGCP::Panel::Role::API::Calls;
+use Moose::Role;
+use Sipwise::Base;
+with 'NGCP::Panel::Role::API' => {
+ -alias =>{ item_rs => '_item_rs', },
+ -excludes => [ 'item_rs' ],
+};
+
+use boolean qw(true);
+use TryCatch;
+use Data::HAL qw();
+use Data::HAL::Link qw();
+use HTTP::Status qw(:constants);
+use NGCP::Panel::Form::Call::Admin;
+use NGCP::Panel::Form::Call::Reseller;
+
+sub item_rs {
+ my ($self, $c) = @_;
+
+ my $item_rs = $c->model('DB')->resultset('cdr');
+ if($c->user->roles eq "admin") {
+ } elsif($c->user->roles eq "reseller") {
+ print ">>>>>>>>>>>>>> filtering s/d_provider_id " . $c->user->reseller->contract_id . "\n";
+ $item_rs = $item_rs->search({
+ -or => [
+ { source_provider_id => $c->user->reseller->contract_id },
+ { destination_provider_id => $c->user->reseller->contract_id },
+ ],
+ });
+ }
+ return $item_rs;
+}
+
+sub get_form {
+ my ($self, $c) = @_;
+ if($c->user->roles eq "admin") {
+ return NGCP::Panel::Form::Call::Admin->new;
+ } elsif($c->user->roles eq "reseller") {
+ return NGCP::Panel::Form::Call::Reseller->new;
+ }
+}
+
+sub hal_from_item {
+ my ($self, $c, $item, $form) = @_;
+ my $resource = $self->resource_from_item($c, $item, $form);
+
+ my $hal = Data::HAL->new(
+ links => [
+ Data::HAL::Link->new(
+ relation => 'curies',
+ href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
+ name => 'ngcp',
+ templated => true,
+ ),
+ Data::HAL::Link->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)),
+ Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
+ Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)),
+ # todo: customer can be in source_account_id or destination_account_id
+# Data::HAL::Link->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->source_customer_id)),
+ ],
+ relation => 'ngcp:'.$self->resource_name,
+ );
+
+ $form //= $self->get_form($c);
+
+ $self->validate_form(
+ c => $c,
+ resource => $resource,
+ form => $form,
+ run => 0,
+ exceptions => [
+ "source_provider_id", "destination_provider_id",
+ "source_external_subscriber_id", "destination_external_subscriber_id",
+ "source_external_contract_id", "destination_external_contract_id",
+ "source_carrier_billing_fee_id", "destination_carrier_billing_fee_id",
+ "source_reseller_billing_fee_id", "destination_reseller_billing_fee_id",
+ "source_customer_billing_fee_id", "destination_customer_billing_fee_id",
+ "source_carrier_billing_zone_id", "destination_carrier_billing_zone_id",
+ "source_reseller_billing_zone_id", "destination_reseller_billing_zone_id",
+ "source_customer_billing_zone_id", "destination_customer_billing_zone_id",
+ ],
+ );
+
+ $resource->{id} = int($item->id);
+ $hal->resource($resource);
+ return $hal;
+}
+
+sub resource_from_item {
+ my ($self, $c, $item, $form) = @_;
+ my $resource = { $item->get_inflated_columns };
+
+ $resource->{source_customer_id} = delete $resource->{source_account_id};
+ $resource->{destination_customer_id} = delete $resource->{destination_account_id};
+
+ if($c->user->roles eq "reseller") {
+ my @filter = ();
+ if($item->source_provider_id ne "".$c->user->reseller->contract_id) {
+ push @filter, (qw/
+ source_user_id source_provider_id
+ source_external_subscriber_id source_external_contract_id
+ source_customer_id source_ip
+ source_reseller_cost source_customer_cost
+ source_reseller_free_time source_customer_free_time
+ source_customer_billing_fee_id source_customer_billing_zone_id
+ /);
+ }
+ if($item->destination_provider_id ne "".$c->user->reseller->contract_id) {
+ push @filter, (qw/
+ destination_user_id destination_provider_id
+ destination_external_subscriber_id destination_external_contract_id
+ destination_customer_id
+ destination_reseller_cost destination_customer_cost
+ destination_reseller_free_time destination_customer_free_time
+ destination_customer_billing_fee_id destination_customer_billing_zone_id
+ /);
+ }
+ for my $f(@filter) {
+ $resource->{$f} = undef if exists($resource->{$f});
+ }
+ }
+
+ return $resource;
+}
+
+sub item_by_id {
+ my ($self, $c, $id) = @_;
+ my $item_rs = $self->item_rs($c);
+ return $item_rs->find($id);
+}
+
+1;
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Role/API/Customers.pm b/lib/NGCP/Panel/Role/API/Customers.pm
index be39901709..42e94acfc9 100644
--- a/lib/NGCP/Panel/Role/API/Customers.pm
+++ b/lib/NGCP/Panel/Role/API/Customers.pm
@@ -100,6 +100,7 @@ sub hal_from_customer {
Data::HAL::Link->new(relation => 'ngcp:contractbalances', href => sprintf("/api/contractbalances/%d", $contract_balance->id)),
$customer->subscriber_email_template_id ? (Data::HAL::Link->new(relation => 'ngcp:subscriberemailtemplates', href => sprintf("/api/emailtemplates/%d", $customer->subscriber_email_template_id))) : (),
$customer->passreset_email_template_id ? (Data::HAL::Link->new(relation => 'ngcp:passresetemailtemplates', href => sprintf("/api/emailtemplates/%d", $customer->passreset_email_template_id))) : (),
+ Data::HAL::Link->new(relation => 'ngcp:calls', href => sprintf("/api/calls/?customer_id=%d", $customer->id)),
],
relation => 'ngcp:'.$self->resource_name,
);
diff --git a/lib/NGCP/Panel/Role/API/Subscribers.pm b/lib/NGCP/Panel/Role/API/Subscribers.pm
index 5b31e1ea85..ca27d30a98 100644
--- a/lib/NGCP/Panel/Role/API/Subscribers.pm
+++ b/lib/NGCP/Panel/Role/API/Subscribers.pm
@@ -104,6 +104,7 @@ sub hal_from_item {
Data::HAL::Link->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->contract_id)),
($item->provisioning_voip_subscriber && $item->provisioning_voip_subscriber->profile_set_id) ? (Data::HAL::Link->new(relation => 'ngcp:subscriberprofilesets', href => sprintf("/api/subscriberprofilesets/%d", $item->provisioning_voip_subscriber->profile_set_id))) : (),
($item->provisioning_voip_subscriber && $item->provisioning_voip_subscriber->profile_id) ? (Data::HAL::Link->new(relation => 'ngcp:subscriberprofiles', href => sprintf("/api/subscriberprofiles/%d", $item->provisioning_voip_subscriber->profile_id))) : (),
+ Data::HAL::Link->new(relation => 'ngcp:calls', href => sprintf("/api/calls/?subscriber_id=%d", $item->id)),
#Data::HAL::Link->new(relation => 'ngcp:registrations', href => sprintf("/api/registrations/%d", $item->contract->id)),
#Data::HAL::Link->new(relation => 'ngcp:trustedsources', href => sprintf("/api/trustedsources/%d", $item->contract->id)),
],
diff --git a/share/templates/api/root/collection.tt b/share/templates/api/root/collection.tt
index 2441eaf723..32ef0a2c06 100644
--- a/share/templates/api/root/collection.tt
+++ b/share/templates/api/root/collection.tt
@@ -13,9 +13,9 @@
[% col.description %]
None.
[% ELSE -%]