From 19a8e0127ce7ed395ab088f041aab77456ab9d36 Mon Sep 17 00:00:00 2001 From: Tomas Cohen Arazi Date: Tue, 5 Sep 2017 16:44:43 -0300 Subject: [PATCH] Bug 18731: /acquisitions/orders endpoint This patches implement the /acquisitions/orders endpoint. The attribute names are consistent with the voted RFC To test: 1. Apply this patchset 2. Run: $ kshell k$ prove t/db_dependent/api/v1/acquisitions_orders.t => SUCCESS: Tests pass! 3. Test the API with your favourite tool (Postman?) => SUCCESS: It works as expected! 4 Sign off :-D Sponsored-by: Camden County Signed-off-by: Matthias Meusburger --- Koha/Acquisition/Order.pm | 46 ++++ Koha/REST/V1/Acquisitions/Orders.pm | 379 ++++++++++++++++++++++++++++ Koha/Schema/Result/Aqorder.pm | 3 + 3 files changed, 428 insertions(+) create mode 100644 Koha/REST/V1/Acquisitions/Orders.pm diff --git a/Koha/Acquisition/Order.pm b/Koha/Acquisition/Order.pm index 9bba0faef2..6476269f18 100644 --- a/Koha/Acquisition/Order.pm +++ b/Koha/Acquisition/Order.pm @@ -263,6 +263,52 @@ sub duplicate_to { return $new_order; } +=head3 to_api_mapping + +This method returns the mapping for representing a Koha::Acquisition::Order object +on the API. + +=cut + +sub to_api_mapping { + return { + basketno => 'basket_id', + biblionumber => 'biblio_id', + budgetdate => undef, # unused + cancellationreason => 'cancellation_reason', + claimed_date => 'last_claim_date', + datecancellationprinted => 'cancellation_date', + datereceived => 'date_received', + discount => 'discount_rate', + entrydate => 'entry_date', + freight => 'shipping_cost', + invoiceid => 'invoice_id', + line_item_id => undef, # EDIFACT related + listprice => 'list_price', + order_internalnote => 'internal_note', + order_vendornote => 'vendor_note', + ordernumber => 'order_id', + orderstatus => 'status', + parent_ordernumber => 'parent_order_id', + purchaseordernumber => undef, # obsolete + quantityreceived => 'quantity_received', + replacementprice => 'replacement_price', + sort1 => 'statistics_1', + sort1_authcat => 'statistics_1_authcat', + sort2 => 'statistics_2', + sort2_authcat => 'statistics_2_authcat', + subscriptionid => 'subscription_id', + suppliers_reference_number => undef, # EDIFACT related + suppliers_reference_qualifier => undef, # EDIFACT related + suppliers_report => undef, # EDIFACT related + tax_rate_bak => undef, # unused + tax_value_bak => undef, # unused + uncertainprice => 'uncertain_price', + unitprice => 'unit_price', + unitprice_tax_excluded => 'unit_price_tax_excluded', + unitprice_tax_included => 'unit_price_tax_included' + }; +} =head2 Internal methods diff --git a/Koha/REST/V1/Acquisitions/Orders.pm b/Koha/REST/V1/Acquisitions/Orders.pm new file mode 100644 index 0000000000..17a6d6191a --- /dev/null +++ b/Koha/REST/V1/Acquisitions/Orders.pm @@ -0,0 +1,379 @@ +package Koha::REST::V1::Acquisitions::Orders; + +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use Koha::Acquisition::Orders; +use Koha::DateUtils; + +use Scalar::Util qw( blessed ); +use Try::Tiny; + +=head1 NAME + +Koha::REST::V1::Acquisitions::Orders + +=head1 API + +=head2 Methods + +=head3 list + +Controller function that handles listing Koha::Acquisition::Order objects + +=cut + +sub list { + + my $c = shift->openapi->valid_input or return; + + return try { + my $orders = $c->objects->search( Koha::Acquisition::Orders->new, \&_to_model, \&_to_api ); + + return $c->render( + status => 200, + openapi => $orders + ); + } + catch { + if ( $_->isa('Koha::Exceptions::Exception') ) { + return $c->render( + status => 500, + openapi => { error => "Unhandled exception ($_)" } + ); + } + else { + return $c->render( + status => 500, + openapi => { error => "Something went wrong, check the logs." } + ); + } + }; +} + +=head3 get + +Controller function that handles retrieving a single Koha::Aquisition::Order object + +=cut + +sub get { + my $c = shift->openapi->valid_input or return; + + my $order = Koha::Acquisition::Orders->find( $c->validation->param('order_id') ); + + unless ($order) { + return $c->render( + status => 404, + openapi => { error => "Order not found" } + ); + } + + return $c->render( + status => 200, + openapi => $order->to_api + ); +} + +=head3 add + +Controller function that handles adding a new Koha::Acquisition::Order object + +=cut + +sub add { + my $c = shift->openapi->valid_input or return; + + return try { + my $order = Koha::Acquisition::Order->new( _to_model( $c->validation->param('body') ) ); + $order->store->discard_changes; + + $c->res->headers->location( + $c->req->url->to_string . '/' . $order->ordernumber + ); + + return $c->render( + status => 201, + openapi => $order->to_api + ); + } + catch { + unless ( blessed $_ && $_->can('rethrow') ) { + return $c->render( + status => 500, + openapi => { + error => + "Something went wrong, check Koha logs for details." + } + ); + } + if ( $_->isa('Koha::Exceptions::Object::DuplicateID') ) { + return $c->render( + status => 409, + openapi => { error => $_->error, conflict => $_->duplicate_id } + ); + } + else { + return $c->render( + status => 500, + openapi => { error => "$_" } + ); + } + }; +} + +=head3 update + +Controller function that handles updating a Koha::Acquisition::Order object + +=cut + +sub update { + my $c = shift->openapi->valid_input or return; + + my $order = Koha::Acquisition::Orders->find( $c->validation->param('order_id') ); + + unless ($order) { + return $c->render( + status => 404, + openapi => { error => "Order not found" } + ); + } + + return try { + $order->set( _to_model($c->validation->param('body')) ); + $order->store()->discard_changes; + + return $c->render( + status => 200, + openapi => $order->to_api + ); + } + catch { + if ( $_->isa('Koha::Exceptions::Exception') ) { + return $c->render( + status => 500, + openapi => { error => "Unhandled exception ($_)" } + ); + } + else { + return $c->render( + status => 500, + openapi => { error => "Something went wrong, check the logs." } + ); + } + }; +} + +=head3 delete + +Controller function that handles deleting a Koha::Patron object + +=cut + +sub delete { + my $c = shift->openapi->valid_input or return; + + my $order = Koha::Acquisition::Orders->find( $c->validation->param('order_id') ); + + unless ($order) { + return $c->render( + status => 404, + openapi => { error => 'Order not found' } + ); + } + + return try { + + $order->delete; + + return $c->render( + status => 204, + openapi => q{} + ); + } + catch { + if ( $_->isa('Koha::Exceptions::Exception') ) { + return $c->render( + status => 500, + openapi => { error => "Unhandled exception ($_)" } + ); + } + else { + return $c->render( + status => 500, + openapi => { error => "Something went wrong, check the logs." } + ); + } + }; +} + +=head3 _to_api + +Helper function that maps unblessed Koha::Acquisition::Order objects into REST api +attribute names. + +=cut + +sub _to_api { + my $order = shift; + + # Rename attributes + foreach my $column ( keys %{ $Koha::REST::V1::Acquisitions::Orders::to_api_mapping } ) { + my $mapped_column = $Koha::REST::V1::Acquisitions::Orders::to_api_mapping->{$column}; + if ( exists $order->{ $column } + && defined $mapped_column ) + { + # key != undef + $order->{ $mapped_column } = delete $order->{ $column }; + } + elsif ( exists $order->{ $column } + && !defined $mapped_column ) + { + # key == undef + delete $order->{ $column }; + } + } + + return $order; +} + +=head3 _to_model + +Helper function that maps REST api objects into Koha::Acquisition::Order +attribute names. + +=cut + +sub _to_model { + my $order = shift; + + foreach my $attribute ( keys %{ $Koha::REST::V1::Acquisitions::Orders::to_model_mapping } ) { + my $mapped_attribute = $Koha::REST::V1::Acquisitions::Orders::to_model_mapping->{$attribute}; + if ( exists $order->{ $attribute } + && defined $mapped_attribute ) + { + # key => !undef + $order->{ $mapped_attribute } = delete $order->{ $attribute }; + } + elsif ( exists $order->{ $attribute } + && !defined $mapped_attribute ) + { + # key => undef / to be deleted + delete $order->{ $attribute }; + } + } + + my @date_fields = ( + 'datecancellationprinted', 'budgetdate', + 'claimed_date', 'entrydate', + 'datereceived', 'timestamp' + ); + foreach my $field (@date_fields) { + $order->{$field} = output_pref( { str => $order->{$field}, dateformat => 'sql' } ) + if exists $order->{$field}; + } + + $order->{uncertainprice} = ( $order->{uncertainprice} ) ? 1 : 0 + if exists $order->{uncertainprice}; + + return $order; +} + +=head2 Global variables + +=head3 $to_api_mapping + +=cut + +our $to_api_mapping = { + basketno => 'basket_id', + biblionumber => 'biblio_id', + budgetdate => undef, # unused + cancellationreason => 'cancellation_reason', + claimed_date => 'last_claim_date', + datecancellationprinted => 'cancellation_date', + datereceived => 'date_received', + discount => 'discount_rate', + entrydate => 'entry_date', + freight => 'shipping_cost', + invoiceid => 'invoice_id', + line_item_id => undef, # EDIFACT related + listprice => 'list_price', + order_internalnote => 'internal_note', + order_vendornote => 'vendor_note', + ordernumber => 'order_id', + orderstatus => 'status', + parent_ordernumber => 'parent_order_id', + purchaseordernumber => undef, # obsolete + quantityreceived => 'quantity_received', + replacementprice => 'replacement_price', + sort1 => 'statistics_1', + sort1_authcat => 'statistics_1_authcat', + sort2 => 'statistics_2', + sort2_authcat => 'statistics_2_authcat', + subscriptionid => 'subscription_id', + suppliers_reference_number => undef, # EDIFACT related + suppliers_reference_qualifier => undef, # EDIFACT related + suppliers_report => undef, # EDIFACT related + tax_rate_bak => undef, # unused + tax_value_bak => undef, # unused + uncertainprice => 'uncertain_price', + unitprice => 'unit_price', + unitprice_tax_excluded => 'unit_price_tax_excluded', + unitprice_tax_included => 'unit_price_tax_included' + +}; + + +=head3 $to_model_mapping + +=cut + +our $to_model_mapping = { + basket_id => 'basketno', + biblio_id => 'biblionumber', + last_claim_date => 'claimed_date', + cancellation_date => 'datecacellationprinted', + discount_rate => 'discount', + cancellation_reason => 'cancellationreason', + date_received => 'datereceived', + entry_date => 'entrydate', + shipping_cost => 'freight', + invoice_id => 'invoiceid', + list_price => 'listprice', + internal_note => 'order_internalnote', + vendor_note => 'order_vendornote', + order_id => 'ordernumber', + status => 'orderstatus', + parent_order_id => 'parent_ordernumber', + quantity_received => 'quantityreceived', + statistics_1 => 'sort1', + statistics_1_authcat => 'sort1_authcat', + statistics_2 => 'sort2', + statistics_2_authcat => 'sort2_authcat', + subscription_id => 'subscriptionid', + uncertain_price => 'uncertainprice', + unit_price => 'unitprice', + unit_price_tax_excluded => 'unitprice_tax_excluded', + unit_price_tax_included => 'unitprice_tax_included' +}; + + +1; diff --git a/Koha/Schema/Result/Aqorder.pm b/Koha/Schema/Result/Aqorder.pm index 803511822d..75917c88f5 100644 --- a/Koha/Schema/Result/Aqorder.pm +++ b/Koha/Schema/Result/Aqorder.pm @@ -670,5 +670,8 @@ sub koha_objects_class { sub koha_object_class { 'Koha::Acquisition::Order'; } +__PACKAGE__->add_columns( + '+uncertainprice' => { is_boolean => 1 } +); 1; -- 2.24.0