From 24f550a01289c686178e9915b5b0816f0233f51e Mon Sep 17 00:00:00 2001 From: Irina Peshinskaya Date: Sun, 2 Jul 2017 18:08:22 +0300 Subject: [PATCH] TT#17848 Invoice API POST request Change-Id: Ibbeccf077c125650547f4e28156325e402d8ec9b --- lib/NGCP/Panel/Controller/API/Invoices.pm | 161 ++++-------------- lib/NGCP/Panel/Controller/API/InvoicesItem.pm | 111 +++--------- lib/NGCP/Panel/Form/Invoice/InvoiceAPI.pm | 30 +++- lib/NGCP/Panel/Role/API/Invoices.pm | 77 ++++----- lib/NGCP/Panel/Role/EntitiesItem.pm | 6 +- t/api-rest/api-invoicetemplates.t | 2 +- 6 files changed, 111 insertions(+), 276 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/Invoices.pm b/lib/NGCP/Panel/Controller/API/Invoices.pm index c4b0ffbdb7..ce8e9051e5 100644 --- a/lib/NGCP/Panel/Controller/API/Invoices.pm +++ b/lib/NGCP/Panel/Controller/API/Invoices.pm @@ -1,24 +1,15 @@ package NGCP::Panel::Controller::API::Invoices; -use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; - -use boolean qw(true); -use NGCP::Panel::Utils::DataHal qw(); -use NGCP::Panel::Utils::DataHalLink qw(); -use HTTP::Headers qw(); +use NGCP::Panel::Utils::Generic qw(:all); use HTTP::Status qw(:constants); -use NGCP::Panel::Utils::DateTime; -use Path::Tiny qw(path); -use Safe::Isa qw($_isa); -require Catalyst::ActionRole::ACL; -require Catalyst::ActionRole::CheckTrailingSlash; -require NGCP::Panel::Role::HTTPMethods; -require Catalyst::ActionRole::RequireSSL; +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::Invoices/; + +__PACKAGE__->set_config(); sub allowed_methods{ - return [qw/GET OPTIONS HEAD/]; + return [qw/GET POST OPTIONS HEAD/]; } sub api_description { @@ -76,126 +67,42 @@ sub query_params { { param => 'serial', description => 'Filter for invoices matching a serial (patterns allowed)', - query => { - first => sub { - my $q = shift; - { serial => { like => $q }}; - }, - second => sub {}, - }, + query_type => 'string_like', }, ]; } -use parent qw/Catalyst::Controller NGCP::Panel::Role::API::Invoices/; - -sub resource_name{ - return 'invoices'; -} -sub dispatch_path{ - return '/api/invoices/'; -} -sub relation{ - return 'http://purl.org/sipwise/ngcp-api/#rel-invoices'; -} - -__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 } - }, -); - -sub gather_default_action_roles { - my ($self, %args) = @_; my @roles = (); - push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method}; - return @roles; -} - -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, $items) = $self->paginate_order_collection($c, $items); - my (@embedded, @links); - my $form = $self->get_form($c); - for my $item ($items->all) { - push @embedded, $self->hal_from_item($c, $item, $form); - push @links, NGCP::Panel::Utils::DataHalLink->new( - relation => 'ngcp:'.$self->resource_name, - href => sprintf('/%s%d', $c->request->path, $item->id), - ); - } - push @links, - NGCP::Panel::Utils::DataHalLink->new( - relation => 'curies', - href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', - name => 'ngcp', - templated => true, - ), - NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), - NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows)); - if(($total_count / $rows) > $page ) { - push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows)); - } - if($page > 1) { - push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows)); - } - - my $hal = NGCP::Panel::Utils::DataHal->new( - embedded => [@embedded], - links => [@links], - ); - $hal->resource({ - total_count => $total_count, +sub create_item { + my ($self, $c, $resource, $form, $process_extras) = @_; + + my $contract_id = $form->values->{customer_id}; + my $tmpl_id = $form->values->{template_id}; + my $period_start = $form->values->{period_start}; + my $period_end = $form->values->{period_end}; + my $period = $form->values->{period}; + my $item; + try { + my($contract_id,$customer,$tmpl,$stime,$etime,$invoice_data) = NGCP::Panel::Utils::Invoice::check_invoice_data($c, { + contract_id => $contract_id, + tmpl_id => $tmpl_id, + period_start => $period_start, + period_end => $period_end, + period => $period, + }); + $item = NGCP::Panel::Utils::Invoice::create_invoice($c,{ + contract_id => $contract_id, + customer => $customer, + stime => $stime, + etime => $etime, + tmpl => $tmpl, + invoice_data => $invoice_data, }); - 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); + } catch($e) { + my $http_code = 'HASH' eq ref $e && $e->{httpcode} ? $e->{httpcode} : HTTP_INTERNAL_SERVER_ERROR; + $self->error($c, $http_code, $e); 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_filtered($c); - $c->response->headers(HTTP::Headers->new( - Allow => join(', ', @{ $allowed_methods }), - 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); + return $item; } 1; diff --git a/lib/NGCP/Panel/Controller/API/InvoicesItem.pm b/lib/NGCP/Panel/Controller/API/InvoicesItem.pm index a30caf9c35..1bfe90e38a 100644 --- a/lib/NGCP/Panel/Controller/API/InvoicesItem.pm +++ b/lib/NGCP/Panel/Controller/API/InvoicesItem.pm @@ -1,107 +1,34 @@ package NGCP::Panel::Controller::API::InvoicesItem; -use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; +use NGCP::Panel::Utils::Generic qw(:all); -use HTTP::Headers qw(); -use HTTP::Status qw(:constants); - -use NGCP::Panel::Utils::DateTime; -use NGCP::Panel::Utils::ValidateJSON qw(); -use Path::Tiny qw(path); -use Safe::Isa qw($_isa); -require Catalyst::ActionRole::ACL; -require NGCP::Panel::Role::HTTPMethods; -require Catalyst::ActionRole::RequireSSL; - -sub allowed_methods{ - return [qw/GET OPTIONS HEAD/]; -} - -use parent qw/Catalyst::Controller NGCP::Panel::Role::API::Invoices/; - -sub resource_name{ - return 'invoices'; -} -sub dispatch_path{ - return '/api/invoices/'; -} -sub relation{ - return 'http://purl.org/sipwise/ngcp-api/#rel-invoices'; -} - -__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 } - }, -); - -sub gather_default_action_roles { - my ($self, %args) = @_; my @roles = (); - push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method}; - return @roles; -} - -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, invoice => $item); +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::Invoices/; - my $hal = $self->hal_from_item($c, $item); +__PACKAGE__->set_config(); - 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 _set_config{ + my ($self, $method) = @_; + return { + log_response => 0, + }; } -sub HEAD :Allow { - my ($self, $c, $id) = @_; - $c->forward(qw(GET)); - $c->response->body(q()); - return; +sub allowed_methods{ + return [qw/GET OPTIONS HEAD DELETE/]; } -sub OPTIONS :Allow { - my ($self, $c, $id) = @_; - my $allowed_methods = $self->allowed_methods_filtered($c); - $c->response->headers(HTTP::Headers->new( - Allow => join(', ', @{ $allowed_methods }), - Accept_Patch => 'application/json-patch+json', - )); - $c->response->content_type('application/json'); - $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n"); - return; +sub process_hal_resource { + my($self, $c, $item, $resource, $form) = @_; + delete $resource->{data}; + return $resource; } -sub end : Private { - my ($self, $c) = @_; - - $self->log_response($c); +sub get_item_binary_data{ + my($self, $c, $id, $item) = @_; + #caller waits for: $data_ref,$mime_type,$filename + #while we will not strictly check Accepted header, if item can return only one type of the binary data + return \$item->data, 'application/pdf', 'invoice_'.$item->id.'.pdf', } 1; diff --git a/lib/NGCP/Panel/Form/Invoice/InvoiceAPI.pm b/lib/NGCP/Panel/Form/Invoice/InvoiceAPI.pm index a46ce94657..5737805450 100644 --- a/lib/NGCP/Panel/Form/Invoice/InvoiceAPI.pm +++ b/lib/NGCP/Panel/Form/Invoice/InvoiceAPI.pm @@ -12,11 +12,20 @@ has_field 'customer_id' => ( title => ['The customer this invoice belongs to.'] }, ); +has_field 'template_id' => ( + type => 'PosInteger', + label => 'Template', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The template is used to generate invoice.'] + }, +); has_field 'serial' => ( type => 'Text', label => 'Serial', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The invoice serial number.'] @@ -26,7 +35,7 @@ has_field 'serial' => ( has_field 'period_start' => ( type => '+NGCP::Panel::Field::DateTime', label => 'Invoice Period Start', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The start of the invoice period.'] @@ -36,17 +45,26 @@ has_field 'period_start' => ( has_field 'period_end' => ( type => '+NGCP::Panel::Field::DateTime', label => 'Invoice Period End', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The end of the invoice period.'] }, ); +has_field 'period' => ( + label => 'Invoice Period End', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Invoice period.'] + }, +); + has_field 'amount_net' => ( type => 'Money', label => 'Net Amount', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The net amount of the invoice in USD, EUR etc.'] @@ -56,7 +74,7 @@ has_field 'amount_net' => ( has_field 'amount_vat' => ( type => 'Money', label => 'VAT Amount', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The vat amount of the invoice in USD, EUR etc.'] @@ -66,7 +84,7 @@ has_field 'amount_vat' => ( has_field 'amount_total' => ( type => 'Money', label => 'Total Amount', - required => 1, + required => 0, element_attr => { rel => ['tooltip'], title => ['The vat amount of the invoice in USD, EUR etc.'] diff --git a/lib/NGCP/Panel/Role/API/Invoices.pm b/lib/NGCP/Panel/Role/API/Invoices.pm index b6bc4842a0..4580f9e7ff 100644 --- a/lib/NGCP/Panel/Role/API/Invoices.pm +++ b/lib/NGCP/Panel/Role/API/Invoices.pm @@ -1,17 +1,33 @@ package NGCP::Panel::Role::API::Invoices; -use NGCP::Panel::Utils::Generic qw(:all); - -use Sipwise::Base; -use parent 'NGCP::Panel::Role::API'; +use parent qw/NGCP::Panel::Role::API/; +use Sipwise::Base; +use NGCP::Panel::Utils::Generic qw(:all); -use boolean qw(true); -use NGCP::Panel::Utils::DataHal qw(); -use NGCP::Panel::Utils::DataHalLink qw(); use HTTP::Status qw(:constants); use NGCP::Panel::Form::Invoice::InvoiceAPI; +sub item_name{ + return 'invoice'; +} + +sub resource_name{ + return 'invoices'; +} + +sub dispatch_path{ + return '/api/invoices/'; +} + +sub relation{ + return 'http://purl.org/sipwise/ngcp-api/#rel-invoices'; +} + +sub config_allowed_roles { + return [qw/admin reseller/]; +} + sub _item_rs { my ($self, $c) = @_; @@ -29,49 +45,14 @@ sub _item_rs { sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::Invoice::InvoiceAPI->new(ctx => $c); -} - -sub hal_from_item { - my ($self, $c, $item, $form) = @_; - my %resource = $item->get_inflated_columns; - $resource{customer_id} = delete $resource{contract_id}; - - my $hal = NGCP::Panel::Utils::DataHal->new( - links => [ - NGCP::Panel::Utils::DataHalLink->new( - relation => 'curies', - href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', - name => 'ngcp', - templated => true, - ), - NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)), - NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), - NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), - NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->contract_id)), - ], - relation => 'ngcp:'.$self->resource_name, - ); - - $form //= $self->get_form($c); - - $self->validate_form( - c => $c, - resource => \%resource, - form => $form, - run => 0, - exceptions => [qw/customer_id/], - ); - - $resource{id} = int($item->id); - $hal->resource({%resource}); - return $hal; + return (NGCP::Panel::Form::Invoice::InvoiceAPI->new(ctx => $c),['customer_id','template_id']); } -sub item_by_id { - my ($self, $c, $id) = @_; - my $item_rs = $self->item_rs($c); - return $item_rs->find($id); +sub hal_links { + my($self, $c, $item, $resource, $form) = @_; + return [ + NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->contract_id)), + ]; } 1; diff --git a/lib/NGCP/Panel/Role/EntitiesItem.pm b/lib/NGCP/Panel/Role/EntitiesItem.pm index 4c26a2fd5a..ca99a3c1d3 100644 --- a/lib/NGCP/Panel/Role/EntitiesItem.pm +++ b/lib/NGCP/Panel/Role/EntitiesItem.pm @@ -33,6 +33,7 @@ sub set_config { } } @{ $self->allowed_methods } }, #action_roles => [qw(HTTPMethods)], + log_response => 1, %{$self->_set_config()}, ); } @@ -186,8 +187,9 @@ sub options { sub end :Private { my ($self, $c) = @_; - - $self->log_response($c); + if($self->config->{log_response}){ + $self->log_response($c); + } } sub GET { diff --git a/t/api-rest/api-invoicetemplates.t b/t/api-rest/api-invoicetemplates.t index 33b29745cf..eb830ce7b1 100644 --- a/t/api-rest/api-invoicetemplates.t +++ b/t/api-rest/api-invoicetemplates.t @@ -20,7 +20,7 @@ $fake_data->set_data_from_script({ 'reseller_id' => sub { return shift->get_id('resellers',@_); }, 'name' => 'api_test invoice template name'.time(), 'type' => 'svg', - 'data' => 'api_test email template', + 'data' => 'api_test invoice template', }, 'query' => ['name'], },