From df8fd1972dfb41cf6bb1f9b3b56b6ee6b0725c14 Mon Sep 17 00:00:00 2001 From: Alex Sassmannshausen Date: Thu, 8 Dec 2016 18:32:04 +0100 Subject: [PATCH] Bug 11897: Add bulk of Stockrotation module. This commit replaces a set of about 20 commits into one large commit. This makes working with the module way easier from bugzilla. As it stands the patches on this bug provide a fully functioning stock rotation feature set for Koha. All previous patches that added functionality were signed off, so I'm retaining that flag. Signed-off-by: Kathleen Milne --- Koha/Item.pm | 49 +- Koha/Library.pm | 46 ++ Koha/REST/V1/Stage.pm | 48 ++ Koha/StockRotationItem.pm | 273 +++++++++++ Koha/StockRotationItems.pm | 124 +++++ Koha/StockRotationRota.pm | 182 +++++++ Koha/StockRotationRotas.pm | 101 ++++ Koha/StockRotationStage.pm | 413 ++++++++++++++++ Koha/StockRotationStages.pm | 86 ++++ Koha/Util/StockRotation.pm | 247 ++++++++++ api/v1/swagger/paths.json | 3 + api/v1/swagger/paths/rotas.json | 79 ++++ catalogue/stockrotation.pl | 175 +++++++ koha-tmpl/intranet-tmpl/prog/css/staff-global.css | 128 +++++ .../prog/en/includes/biblio-view-menu.inc | 1 + .../prog/en/includes/stockrotation-toolbar.inc | 13 + .../intranet-tmpl/prog/en/includes/tools-menu.inc | 22 + .../en/modules/admin/preferences/circulation.pref | 2 +- .../prog/en/modules/catalogue/stockrotation.tt | 157 ++++++ .../prog/en/modules/tools/stockrotation.tt | 482 +++++++++++++++++++ .../prog/en/modules/tools/tools-home.tt | 7 + .../intranet-tmpl/prog/js/pages/stockrotation.js | 65 +++ misc/cronjobs/stockrotation.pl | 489 +++++++++++++++++++ t/db_dependent/Items.t | 61 +++ t/db_dependent/Koha/Libraries.t | 28 +- t/db_dependent/StockRotationItems.t | 393 +++++++++++++++ t/db_dependent/StockRotationRotas.t | 175 +++++++ t/db_dependent/StockRotationStages.t | 377 +++++++++++++++ t/db_dependent/api/v1/stockrotationstage.t | 172 +++++++ tools/stockrotation.pl | 526 +++++++++++++++++++++ 30 files changed, 4921 insertions(+), 3 deletions(-) create mode 100644 Koha/REST/V1/Stage.pm create mode 100644 Koha/StockRotationItem.pm create mode 100644 Koha/StockRotationItems.pm create mode 100644 Koha/StockRotationRota.pm create mode 100644 Koha/StockRotationRotas.pm create mode 100644 Koha/StockRotationStage.pm create mode 100644 Koha/StockRotationStages.pm create mode 100644 Koha/Util/StockRotation.pm create mode 100644 api/v1/swagger/paths/rotas.json create mode 100755 catalogue/stockrotation.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/stockrotation-toolbar.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/stockrotation.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/stockrotation.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/js/pages/stockrotation.js create mode 100755 misc/cronjobs/stockrotation.pl create mode 100644 t/db_dependent/StockRotationItems.t create mode 100644 t/db_dependent/StockRotationRotas.t create mode 100644 t/db_dependent/StockRotationStages.t create mode 100644 t/db_dependent/api/v1/stockrotationstage.t create mode 100755 tools/stockrotation.pl diff --git a/Koha/Item.pm b/Koha/Item.pm index bea05f8317..cba8d5adf6 100644 --- a/Koha/Item.pm +++ b/Koha/Item.pm @@ -30,6 +30,8 @@ use Koha::IssuingRules; use Koha::Item::Transfer; use Koha::Patrons; use Koha::Libraries; +use Koha::StockRotationItem; +use Koha::StockRotationRotas; use base qw(Koha::Object); @@ -235,7 +237,52 @@ sub current_holds { return Koha::Holds->_new_from_dbic($hold_rs); } -=head3 type +=head3 stockrotationitem + + my $sritem = Koha::Item->stockrotationitem; + +Returns the stock rotation item associated with the current item. + +=cut + +sub stockrotationitem { + my ( $self ) = @_; + my $rs = $self->_result->stockrotationitem; + return 0 if !$rs; + return Koha::StockRotationItem->_new_from_dbic( $rs ); +} + +=head3 add_to_rota + + my $item = $item->add_to_rota($rota_id); + +Add this item to the rota identified by $ROTA_ID, which means associating it +with the first stage of that rota. Should this item already be associated +with a rota, then we will move it to the new rota. + +=cut + +sub add_to_rota { + my ( $self, $rota_id ) = @_; + Koha::StockRotationRotas->find($rota_id)->add_item($self->itemnumber); + return $self; +} + +=head3 biblio + + my $biblio = $item->biblio; + +Returns the biblio associated with the current item. + +=cut + +sub biblio { + my ( $self ) = @_; + my $rs = $self->_result->biblio; + return Koha::Biblio->_new_from_dbic( $rs ); +} + +=head3 _type =cut diff --git a/Koha/Library.pm b/Koha/Library.pm index 9e6d616036..7877174ec5 100644 --- a/Koha/Library.pm +++ b/Koha/Library.pm @@ -24,6 +24,7 @@ use Carp; use C4::Context; use Koha::Database; +use Koha::StockRotationStages; use base qw(Koha::Object); @@ -41,6 +42,51 @@ TODO: Ask the author to add a proper description =cut +sub get_categories { + my ( $self, $params ) = @_; + # TODO This should return Koha::LibraryCategories + return $self->{_result}->categorycodes( $params ); +} + +=head3 update_categories + +TODO: Ask the author to add a proper description + +=cut + +sub update_categories { + my ( $self, $categories ) = @_; + $self->_result->delete_related( 'branchrelations' ); + $self->add_to_categories( $categories ); +} + +=head3 add_to_categories + +TODO: Ask the author to add a proper description + +=cut + +sub add_to_categories { + my ( $self, $categories ) = @_; + for my $category ( @$categories ) { + $self->_result->add_to_categorycodes( $category->_result ); + } +} + +=head3 stockrotationstages + + my $stages = Koha::Library->stockrotationstages; + +Returns the stockrotation stages associated with this Library. + +=cut + +sub stockrotationstages { + my ( $self ) = @_; + my $rs = $self->_result->stockrotationstages; + return Koha::StockRotationStages->_new_from_dbic( $rs ); +} + =head3 get_effective_marcorgcode my $marcorgcode = Koha::Libraries->find( $library_id )->get_effective_marcorgcode(); diff --git a/Koha/REST/V1/Stage.pm b/Koha/REST/V1/Stage.pm new file mode 100644 index 0000000000..fa4ca7ad2b --- /dev/null +++ b/Koha/REST/V1/Stage.pm @@ -0,0 +1,48 @@ +package Koha::REST::V1::Stage; + +# 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::StockRotationRotas; +use Koha::StockRotationStages; + +sub move { + my $c = shift->openapi->valid_input or return; + my $input = $c->validation->output; + + my $rota = Koha::StockRotationRotas->find( $input->{rota_id} ); + my $stage = Koha::StockRotationStages->find( $input->{stage_id} ); + + if ( $stage && $rota ) { + my $result = $stage->move_to( $input->{position} ); + return $c->render( openapi => {}, status => 200 ) if $result; + return $c->render( + openapi => { error => "Bad request - new position invalid" }, + status => 400 + ); + } + else { + return $c->render( + openapi => { error => "Not found - Invalid rota or stage ID" }, + status => 404 + ); + } +} + +1; diff --git a/Koha/StockRotationItem.pm b/Koha/StockRotationItem.pm new file mode 100644 index 0000000000..e451bb08db --- /dev/null +++ b/Koha/StockRotationItem.pm @@ -0,0 +1,273 @@ +package Koha::StockRotationItem; + +# Copyright PTFS Europe 2016 +# +# 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 DateTime; +use DateTime::Duration; +use Koha::Database; +use Koha::DateUtils qw/dt_from_string/; +use Koha::Item::Transfer; +use Koha::Item; +use Koha::StockRotationStage; + +use base qw(Koha::Object); + +=head1 NAME + +StockRotationItem - Koha StockRotationItem Object class + +=head1 SYNOPSIS + +StockRotationItem class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationitem'; +} + +=head3 itemnumber + + my $item = Koha::StockRotationItem->itemnumber; + +Returns the item associated with the current stock rotation item. + +=cut + +sub itemnumber { + my ( $self ) = @_; + my $rs = $self->_result->itemnumber; + return Koha::Item->_new_from_dbic( $rs ); +} + +=head3 stage + + my $stage = Koha::StockRotationItem->stage; + +Returns the stage associated with the current stock rotation item. + +=cut + +sub stage { + my ( $self ) = @_; + my $rs = $self->_result->stage; + return Koha::StockRotationStage->_new_from_dbic( $rs ); +} + +=head3 needs_repatriating + + 1|0 = $item->needs_repatriating; + +Return 1 if this item is currently not at the library it should be at +according to our stockrotation plan. + +=cut + +sub needs_repatriating { + my ( $self ) = @_; + my ( $item, $stage ) = ( $self->itemnumber, $self->stage ); + if ( $self->itemnumber->get_transfer ) { + return 0; # We're in transit. + } elsif ( $item->holdingbranch ne $stage->branchcode_id + || $item->homebranch ne $stage->branchcode_id ) { + return 1; # We're not where we should be. + } else { + return 0; # We're at home. + } +} + +=head3 needs_advancing + + 1|0 = $item->needs_advancing; + +Return 1 if this item is ready to be moved on to the next stage in its rota. + +=cut + +sub needs_advancing { + my ( $self ) = @_; + return 0 if $self->itemnumber->get_transfer; # intransfer: don't advance. + return 1 if $self->fresh; # Just on rota: advance. + my $completed = $self->itemnumber->_result->branchtransfers->search( + { 'comments' => "StockrotationAdvance" }, + { order_by => { -desc => 'datearrived' } } + ); + # Do maths on whether we need to be moved on. + if ( $completed->count ) { + my $arrival = dt_from_string( + $completed->next->datearrived, 'iso' + ); + my $duration = DateTime::Duration + ->new( days => $self->stage->duration ); + if ( $arrival + $duration le DateTime->now ) { + return 1; + } else { + return 0; + } + } else { + die "We have no historical branch transfer; this should not have happened!"; + } +} + +=head3 repatriate + + 1|0 = $sritem->repatriate + +Put this item into branch transfer with 'StockrotationCorrection' comment, so +that it may return to it's stage.branch to continue its rota as normal. + +=cut + +sub repatriate { + my ( $self, $msg ) = @_; + # Create the transfer. + my $transfer_stored = Koha::Item::Transfer->new({ + 'itemnumber' => $self->itemnumber_id, + 'frombranch' => $self->itemnumber->holdingbranch, + 'tobranch' => $self->stage->branchcode_id, + 'datesent' => DateTime->now, + 'comments' => $msg || "StockrotationRepatriation", + })->store; + $self->itemnumber->homebranch($self->stage->branchcode_id)->store; + return $transfer_stored; +} + +=head3 advance + + 1|0 = $sritem->advance; + +Put this item into branch transfer with 'StockrotationAdvance' comment, to +transfer it to the next stage in its rota. + +If this is the last stage in the rota and this rota is cyclical, we return to +the first stage. If it is not cyclical, then we delete this +StockRotationItem. + +If this item is 'indemand', and advance is invoked, we disable 'indemand' and +advance the item as per usual. + +=cut + +sub advance { + my ( $self ) = @_; + my $item = $self->itemnumber; + my $transfer = Koha::Item::Transfer->new({ + 'itemnumber' => $self->itemnumber_id, + 'frombranch' => $item->holdingbranch, + 'datesent' => DateTime->now, + 'comments' => "StockrotationAdvance" + }); + + if ( $self->indemand && !$self->fresh ) { + $self->indemand(0)->store; # De-activate indemand + $transfer->tobranch($self->stage->branchcode_id); + $transfer->datearrived(DateTime->now); + } else { + # Find and update our stage. + my $stage = $self->stage; + my $new_stage; + if ( $self->fresh ) { # Just added to rota + $new_stage = $self->stage->first_sibling || $self->stage; + $transfer->tobranch($new_stage->branchcode_id); + $transfer->datearrived(DateTime->now) # Already at first branch + if $item->holdingbranch eq $new_stage->branchcode_id; + $self->fresh(0)->store; # Reset fresh + } elsif ( !$stage->last_sibling ) { # Last stage + if ( $stage->rota->cyclical ) { # Cyclical rota? + # Revert to first stage. + $new_stage = $stage->first_sibling || $stage; + $transfer->tobranch($new_stage->branchcode_id); + $transfer->datearrived(DateTime->now); + } else { + $self->delete; # StockRotationItem is done. + return 1; + } + } else { + # Just advance. + $new_stage = $self->stage->next_sibling; + } + $self->stage_id($new_stage->stage_id)->store; # Set new stage + $item->homebranch($new_stage->branchcode_id)->store; # Update homebranch + $transfer->tobranch($new_stage->branchcode_id); # Send to new branch + } + + return $transfer->store; +} + +=head3 investigate + + my $report = $item->investigate; + +Return the base set of information, namely this individual item's report, for +generating stockrotation reports about this stockrotationitem. + +=cut + +sub investigate { + my ( $self ) = @_; + my $item_report = { + title => $self->itemnumber->_result->biblioitem + ->biblionumber->title, + author => $self->itemnumber->_result->biblioitem + ->biblionumber->author, + callnumber => $self->itemnumber->itemcallnumber, + location => $self->itemnumber->location, + onloan => $self->itemnumber->onloan, + barcode => $self->itemnumber->barcode, + itemnumber => $self->itemnumber_id, + branch => $self->itemnumber->_result->holdingbranch, + object => $self, + }; + my $reason; + if ( $self->fresh ) { + $reason = 'initiation'; + } elsif ( $self->needs_repatriating ) { + $reason = 'repatriation'; + } elsif ( $self->needs_advancing ) { + $reason = 'advancement'; + $reason = 'in-demand' if $self->indemand; + } else { + $reason = 'not-ready'; + } + $item_report->{reason} = $reason; + + return $item_report; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/StockRotationItems.pm b/Koha/StockRotationItems.pm new file mode 100644 index 0000000000..8fde691ae4 --- /dev/null +++ b/Koha/StockRotationItems.pm @@ -0,0 +1,124 @@ +package Koha::StockRotationItems; + +# Copyright PTFS Europe 2016 +# +# 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 Koha::Database; +use Koha::StockRotationItem; + +use base qw(Koha::Objects); + +=head1 NAME + +StockRotationItems - Koha StockRotationItems Object class + +=head1 SYNOPSIS + +StockRotationItems class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationitem'; +} + +sub object_class { + return 'Koha::StockRotationItem'; +} + +=head3 investigate + + my $report = $items->investigate; + +Return a stockrotation report about this set of stockrotationitems. + +In this part of the overall investigation process we split individual item +reports into appropriate action segments of our items report and increment +some counters. + +The report generated here will be used on the stage level to slot our item +reports into appropriate sections of the branched report. + +For details of intent and context of this procedure, please see +Koha::StockRotationRota->investigate. + +=cut + +sub investigate { + my ( $self ) = @_; + + my $items_report = { + items => [], + log => [], + initiable_items => [], + repatriable_items => [], + advanceable_items => [], + indemand_items => [], + actionable => 0, + stationary => 0, + }; + while ( my $item = $self->next ) { + my $report = $item->investigate; + if ( $report->{reason} eq 'initiation' ) { + $items_report->{initiable}++; + $items_report->{actionable}++; + push @{$items_report->{items}}, $report; + push @{$items_report->{initiable_items}}, $report; + } elsif ( $report->{reason} eq 'repatriation' ) { + $items_report->{repatriable}++; + $items_report->{actionable}++; + push @{$items_report->{items}}, $report; + push @{$items_report->{repatriable_items}}, $report; + } elsif ( $report->{reason} eq 'advancement' ) { + $items_report->{actionable}++; + push @{$items_report->{items}}, $report; + push @{$items_report->{advanceable_items}}, $report; + } elsif ( $report->{reason} eq 'in-demand' ) { + $items_report->{actionable}++; + push @{$items_report->{items}}, $report; + push @{$items_report->{indemand_items}}, $report; + } else { + $items_report->{stationary}++; + push @{$items_report->{log}}, $report; + } + } + + return $items_report; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/StockRotationRota.pm b/Koha/StockRotationRota.pm new file mode 100644 index 0000000000..fdf52fccd9 --- /dev/null +++ b/Koha/StockRotationRota.pm @@ -0,0 +1,182 @@ +package Koha::StockRotationRota; + +# Copyright PTFS Europe 2016 +# +# 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 Koha::Database; +use Koha::StockRotationStages; +use Koha::StockRotationItem; +use Koha::StockRotationItems; + +use base qw(Koha::Object); + +=head1 NAME + +StockRotationRota - Koha StockRotationRota Object class + +=head1 SYNOPSIS + +StockRotationRota class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 stockrotationstages + + my $stages = Koha::StockRotationRota->stockrotationstages; + +Returns the stages associated with the current rota. + +=cut + +sub stockrotationstages { + my ( $self ) = @_; + my $rs = $self->_result->stockrotationstages; + return Koha::StockRotationStages->_new_from_dbic( $rs ); +} + +=head3 add_item + + my $rota = $rota->add_item($itemnumber); + +Add item identified by $ITEMNUMBER to this rota, which means we associate it +with the first stage of this rota. Should the item already be associated with +a rota, move it from that rota to this rota. + +=cut + +sub add_item { + my ( $self, $itemnumber ) = @_; + my $sritem = Koha::StockRotationItems->find($itemnumber); + if ($sritem) { + $sritem->stage_id($self->first_stage->stage_id) + ->indemand(0)->fresh(1)->store; + } else { + $sritem = Koha::StockRotationItem->new({ + itemnumber_id => $itemnumber, + stage_id => $self->first_stage->stage_id, + indemand => 0, + fresh => 1, + })->store; + } + return $self; +} + +=head3 first_stage + + my $stage = $rota->first_stage; + +Return the first stage attached to this rota (the one that has an undefined +`stagebefore`). + +=cut + +sub first_stage { + my ( $self ) = @_; + my $guess = $self->stockrotationstages->next; + my $stage = $guess->first_sibling; + return ( $stage ) ? $stage : $guess; +} + +=head3 stockrotationitems + + my $items = $rota->stockrotationitems; + +Return all items associated with this rota via its stages. + +=cut + +sub stockrotationitems { + my ( $self ) = @_; + my $rs = Koha::StockRotationItems->search( + { 'stage.rota_id' => $self->rota_id }, { join => [ qw/stage/ ] } + ); + return $rs; +} + +=head3 investigate + + my $report = $rota->investigate($report_so_far); + +Aim here is to return $report augmented with content for this rota. We +delegate to $stage->investigate. + +The report will include some basic information and 2 primary reports: + +- per rota report in 'rotas'. This report is mainly used by admins to do check + & compare results. + +- branched report in 'branched'. This is the workhorse: emails to libraries + are compiled from these reports, and they will have the actionable work. + +Both reports are generated in stage based investigations; the rota report is +then glued into place at this stage. + +=cut + +sub investigate { + my ( $self, $report ) = @_; + my $count = $self->stockrotationitems->count; + $report->{sum_items} += $count; + + if ( $self->active ) { + $report->{rotas_active}++; + # stockrotationstages->investigate augments $report with the stage's + # content. This is how 'branched' slowly accumulates all items. + $report = $self->stockrotationstages->investigate($report); + # Add our rota report to the full report. + push @{$report->{rotas}}, { + name => $self->title, + id => $self->rota_id, + items => $report->{tmp_items} || [], + log => $report->{tmp_log} || [], + }; + delete $report->{tmp_items}; + delete $report->{tmp_log}; + } else { # Rota is not active. + $report->{rotas_inactive}++; + $report->{items_inactive} += $count; + } + + return $report; +} + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationrota'; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/StockRotationRotas.pm b/Koha/StockRotationRotas.pm new file mode 100644 index 0000000000..c308a9246b --- /dev/null +++ b/Koha/StockRotationRotas.pm @@ -0,0 +1,101 @@ +package Koha::StockRotationRotas; + +# Copyright PTFS Europe 2016 +# +# 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 Koha::Database; +use Koha::StockRotationRota; + +use base qw(Koha::Objects); + +=head1 NAME + +StockRotationRotas - Koha StockRotationRotas Object class + +=head1 SYNOPSIS + +StockRotationRotas class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 investigate + + my $report = $rotas->investigate; + +Return a report detailing the current status and required actions for all +relevant items spread over rotas. + +See Koha::StockRotationRota->investigate for details. + +=cut + +sub investigate { + my ( $self ) = @_; + + my $report = { + actionable => 0, + advanceable => 0, + initiable => 0, + items_inactive => 0, + repatriable => 0, + rotas_active => 0, + rotas_inactive => 0, + stationary => 0, + sum_items => 0, + sum_rotas => $self->count, + branched => {}, + rotas => [], + items => [], + }; + + while ( my $rota = $self->next ) { + $report = $rota->investigate($report) + } + + return $report; +} + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationrota'; +} + +sub object_class { + return 'Koha::StockRotationRota'; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/StockRotationStage.pm b/Koha/StockRotationStage.pm new file mode 100644 index 0000000000..eadb71686e --- /dev/null +++ b/Koha/StockRotationStage.pm @@ -0,0 +1,413 @@ +package Koha::StockRotationStage; + +# Copyright PTFS Europe 2016 +# +# 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 Koha::Database; +use Koha::Library; +use Koha::StockRotationRota; + +use base qw(Koha::Object); + +=head1 NAME + +StockRotationStage - Koha StockRotationStage Object class + +=head1 SYNOPSIS + +StockRotationStage class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationstage'; +} + +sub _relation { + my ( $self, $method, $type ) = @_; + return sub { + my $rs = $self->_result->$method; + return 0 if !$rs; + my $namespace = 'Koha::' . $type; + return $namespace->_new_from_dbic( $rs ); + } +} + +=head3 stockrotationitems + + my $stages = Koha::StockRotationStage->stockrotationitems; + +Returns the items associated with the current stage. + +=cut + +sub stockrotationitems { + my ( $self ) = @_; + return &{$self->_relation(qw/ stockrotationitems StockRotationItems /)}; +} + +=head3 branchcode + + my $branch = Koha::StockRotationStage->branchcode; + +Returns the branch associated with the current stage. + +=cut + +sub branchcode { + my ( $self ) = @_; + return &{$self->_relation(qw/ branchcode Library /)}; +} + +=head3 rota + + my $rota = Koha::StockRotationStage->rota; + +Returns the rota associated with the current stage. + +=cut + +sub rota { + my ( $self ) = @_; + return &{$self->_relation(qw/ rota StockRotationRota /)}; +} + +=head3 siblings + + my $siblings = $stage->siblings; + +Koha::Object wrapper around DBIx::Class::Ordered. + +=cut + +sub siblings { + my ( $self ) = @_; + return &{$self->_relation(qw/ siblings StockRotationStages /)}; +} + +=head3 next_siblings + + my $next_siblings = $stage->next_siblings; + +Koha::Object wrapper around DBIx::Class::Ordered. + +=cut + +sub next_siblings { + my ( $self ) = @_; + return &{$self->_relation(qw/ next_siblings StockRotationStages /)}; +} + +=head3 previous_siblings + + my $previous_siblings = $stage->previous_siblings; + +Koha::Object wrapper around DBIx::Class::Ordered. + +=cut + +sub previous_siblings { + my ( $self ) = @_; + return &{$self->_relation(qw/ previous_siblings StockRotationStages /)}; +} + +=head3 next_sibling + + my $next = $stage->next_sibling; + +Koha::Object wrapper around DBIx::Class::Ordered. + +=cut + +sub next_sibling { + my ( $self ) = @_; + return &{$self->_relation(qw/ next_sibling StockRotationStage /)}; +} + +=head3 previous_sibling + + my $previous = $stage->previous_sibling; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub previous_sibling { + my ( $self ) = @_; + return &{$self->_relation(qw/ previous_sibling StockRotationStage /)}; +} + +=head3 first_sibling + + my $first = $stage->first_sibling; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub first_sibling { + my ( $self ) = @_; + return &{$self->_relation(qw/ first_sibling StockRotationStage /)}; +} + +=head3 last_sibling + + my $last = $stage->last_sibling; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub last_sibling { + my ( $self ) = @_; + return &{$self->_relation(qw/ last_sibling StockRotationStage /)}; +} + +=head3 move_previous + + 1|0 = $stage->move_previous; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_previous { + my ( $self ) = @_; + return $self->_result->move_previous; +} + +=head3 move_next + + 1|0 = $stage->move_next; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_next { + my ( $self ) = @_; + return $self->_result->move_next; +} + +=head3 move_first + + 1|0 = $stage->move_first; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_first { + my ( $self ) = @_; + return $self->_result->move_first; +} + +=head3 move_last + + 1|0 = $stage->move_last; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_last { + my ( $self ) = @_; + return $self->_result->move_last; +} + +=head3 move_to + + 1|0 = $stage->move_to($position); + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_to { + my ( $self, $position ) = @_; + return $self->_result->move_to($position) + if ( $position le $self->rota->stockrotationstages->count ); + return 0; +} + +=head3 move_to_group + + 1|0 = $stage->move_to_group($rota_id, [$position]); + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub move_to_group { + my ( $self, $rota_id, $position ) = @_; + return $self->_result->move_to_group($rota_id, $position); +} + +=head3 delete + + 1|0 = $stage->delete; + +Koha::Object Wrapper around DBIx::Class::Ordered. + +=cut + +sub delete { + my ( $self ) = @_; + return $self->_result->delete; +} + +=head3 investigate + + my $report = $stage->investigate($report_so_far); + +Return a stage based report. This report will mutate and augment the report +that is passed to it. It slots item reports into the branched and temporary +rota sections of the report. It also increments a number of counters. + +For details of intent and context of this procedure, please see +Koha::StockRotationRota->investigate. + +=cut + +sub investigate { + my ( $self, $report ) = @_; + my $new_stage = $self->next_sibling; + my $duration = $self->duration; + # Generate stage items report + my $items_report = $self->stockrotationitems->investigate; + + # Merge into general report + + ## Branched indexes + ### The branched indexes work as follows: + ### - They contain information about the relevant branch + ### - They contain an index of actionable items for that branch + ### - They contain an index of non-actionable items for that branch + + ### Items are assigned to a particular branched index as follows: + ### - 'advanceable' : assigned to branch of the current stage + ### (this should also be the current holding branch) + ### - 'log' items are always assigned to branch of current stage. + ### - 'indemand' : assigned to branch of current stage + ### (this should also be the current holding branch) + ### - 'initiable' : assigned to the current holding branch of item + ### - 'repatriable' : assigned to the current holding branch of item + + ### 'Advanceable', 'log', 'indemand': + + # Set up our stage branch info. + my $stagebranch = $self->_result->branchcode; + my $stagebranchcode = $stagebranch->branchcode; + + # Initiate our stage branch index if it does not yet exist. + if ( !$report->{branched}->{$stagebranchcode} ) { + $report->{branched}->{$stagebranchcode} = { + code => $stagebranchcode, + name => $stagebranch->branchname, + email => $stagebranch->branchemail, + phone => $stagebranch->branchphone, + items => [], + log => [], + }; + } + + push @{$report->{branched}->{$stagebranchcode}->{items}}, + @{$items_report->{advanceable_items}}; + push @{$report->{branched}->{$stagebranchcode}->{log}}, + @{$items_report->{log}}; + push @{$report->{branched}->{$stagebranchcode}->{items}}, + @{$items_report->{indemand_items}}; + + ### 'Initiable' & 'Repatriable' + foreach my $ireport (@{$items_report->{initiable_items}}) { + my $branch = $ireport->{branch}; + my $branchcode = $branch->branchcode; + if ( !$report->{branched}->{$branchcode} ) { + $report->{branched}->{$branchcode} = { + code => $branchcode, + name => $branch->branchname, + email => $branch->branchemail, + phone => $branch->branchphone, + items => [], + log => [], + }; + } + push @{$report->{branched}->{$branchcode}->{items}}, $ireport; + } + + foreach my $ireport (@{$items_report->{repatriable_items}}) { + my $branch = $ireport->{branch}; + my $branchcode = $branch->branchcode; + if ( !$report->{branched}->{$branchcode} ) { + $report->{branched}->{$branchcode} = { + code => $branchcode, + name => $branch->branchname, + email => $branch->branchemail, + phone => $branch->branchphone, + items => [], + log => [], + }; + } + push @{$report->{branched}->{$branchcode}->{items}}, $ireport; + } + + ## Per rota indexes + ### Per rota indexes are item reports pushed into the index for the + ### current rota. We don't know where that index is yet as we don't know + ### about the current rota. To resolve this we assign our items and log + ### to tmp indexes. They will be merged into the proper rota index at the + ### rota level. + push @{$report->{tmp_items}}, @{$items_report->{items}}; + push @{$report->{tmp_log}}, @{$items_report->{log}}; + + ## Collection of items + ### Finally we just add our collection of items to the full item index. + push @{$report->{items}}, @{$items_report->{items}}; + + ## Assemble counters + $report->{actionable} += $items_report->{actionable}; + $report->{indemand} += scalar @{$items_report->{indemand_items}}; + $report->{advanceable} += scalar @{$items_report->{advanceable_items}}; + $report->{initiable} += scalar @{$items_report->{initiable_items}}; + $report->{repatriable} += scalar @{$items_report->{repatriable_items}}; + $report->{stationary} += scalar @{$items_report->{log}}; + + return $report; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/StockRotationStages.pm b/Koha/StockRotationStages.pm new file mode 100644 index 0000000000..00faaab28d --- /dev/null +++ b/Koha/StockRotationStages.pm @@ -0,0 +1,86 @@ +package Koha::StockRotationStages; + +# Copyright PTFS Europe 2016 +# +# 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 Koha::Database; +use Koha::StockRotationStage; + +use base qw(Koha::Objects); + +=head1 NAME + +StockRotationStages - Koha StockRotationStages Object class + +=head1 SYNOPSIS + +StockRotationStages class used primarily by stockrotation .pls and the stock +rotation cron script. + +=head1 DESCRIPTION + +Standard Koha::Objects definitions, and additional methods. + +=head1 API + +=head2 Class Methods + +=cut + +=head3 investigate + + my $report = $stages->investigate($rota_so_far); + +Return a report detailing the current status and required actions for all +relevant items spread over the set of stages. + +For details of intent and context of this procedure, please see +Koha::StockRotationRota->investigate. + +=cut + +sub investigate { + my ( $self, $report ) = @_; + + while ( my $stage = $self->next ) { + $report = $stage->investigate($report); + } + + return $report; +} + +=head3 _type + +=cut + +sub _type { + return 'Stockrotationstage'; +} + +sub object_class { + return 'Koha::StockRotationStage'; +} + +1; + +=head1 AUTHOR + +Alex Sassmannshausen + +=cut diff --git a/Koha/Util/StockRotation.pm b/Koha/Util/StockRotation.pm new file mode 100644 index 0000000000..13c78e5675 --- /dev/null +++ b/Koha/Util/StockRotation.pm @@ -0,0 +1,247 @@ +package Koha::Util::StockRotation; + +# Module contains subroutines used with Stock Rotation +# +# Copyright 2016 PTFS-Europe Ltd +# +# 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 Koha::Items; +use Koha::StockRotationItems; +use Koha::Database; + +our ( @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS ); +BEGIN { + require Exporter; + @ISA = qw( Exporter ); + @EXPORT = qw( ); + @EXPORT_OK = qw( + get_branches + get_stages + toggle_indemand + remove_from_stage + get_barcodes_status + add_items_to_rota + move_to_next_stage + ); + %EXPORT_TAGS = ( ALL => [ @EXPORT_OK, @EXPORT ] ); +} + +=head1 NAME + +Koha::Util::StockRotation - utility class with routines for Stock Rotation + +=head1 FUNCTIONS + +=head2 get_branches + + returns all branches ordered by branchname as an array, each element + contains a hashref containing branch details + +=cut + +sub get_branches { + + return Koha::Libraries->search( + {}, + { order_by => ['branchname'] } + )->unblessed; + +} + +=head2 get_stages + + returns an arrayref of StockRotationStage objects representing + all stages for a passed rota + +=cut + +sub get_stages { + + my $rota = shift; + + my @out = (); + + if ($rota->stockrotationstages->count > 0) { + + push @out, $rota->first_stage->unblessed; + + push @out, @{$rota->first_stage->siblings->unblessed}; + + } + + return \@out; +} + +=head2 toggle_indemand + + given an item's ID & stage ID toggle that item's in_demand + status on that stage + +=cut + +sub toggle_indemand { + + my ($item_id, $stage_id) = @_; + + # Get the item object + my $item = Koha::StockRotationItems->find( + { + itemnumber_id => $item_id, + stage_id => $stage_id + } + ); + + # Toggle the item's indemand flag + my $new_indemand = ($item->indemand == 1) ? 0 : 1; + + $item->indemand($new_indemand)->store; + +} + +=head2 move_to_next_stage + + given an item's ID and stage ID, move it + to the next stage on the rota + +=cut + +sub move_to_next_stage { + + my ($item_id, $stage_id) = shift; + + # Get the item object + my $item = Koha::StockRotationItems->find( + { + itemnumber_id => $item_id, + stage_id => $stage_id + } + ); + + $item->advance; + +} + +=head2 remove_from_stage + + given an item's ID & stage ID, remove that item from that stage + +=cut + +sub remove_from_stage { + + my ($item_id, $stage_id) = @_; + + # Get the item object and delete it + Koha::StockRotationItems->find( + { + itemnumber_id => $item_id, + stage_id => $stage_id + } + )->delete; + +} + +=head2 get_barcodes_status + + take an arrayref of barcodes and a status hashref and populate it + +=cut + +sub get_barcodes_status { + + my ($rota_id, $barcodes, $status) = @_; + + # Get the items associated with these barcodes + my $items = Koha::Items->search( + { + barcode => { '-in' => $barcodes } + }, + { + prefetch => 'stockrotationitem' + } + ); + # Get an array of barcodes that were found + # Assign each barcode's status + my @found = (); + while (my $item = $items->next) { + + push @found, $item->barcode if $item->barcode; + + # Check if it's on a rota + my $on_rota = $item->stockrotationitem; + + # It is on a rota + if ($on_rota) { + + # Check if it's on this rota + if ($on_rota->stage->rota->rota_id == $rota_id) { + + # It's on this rota + push @{$status->{on_this}}, $item; + + } else { + + # It's on another rota + push @{$status->{on_other}}, $item; + + } + + } else { + + # Item is not on a rota + push @{$status->{ok}}, $item; + + } + + } + + # Create an array of barcodes supplied in the file that + # were not found in the catalogue + my %found_in_cat = map{ $_ => 1 } @found; + push @{$status->{not_found}}, grep( + !defined $found_in_cat{$_}, @{$barcodes} + ); + +} + +=head2 add_items_to_rota + + take an arrayref of Koha::Item objects and add them to the passed rota + +=cut + +sub add_items_to_rota { + + my ($rota_id, $items) = @_; + + foreach my $item(@{$items}) { + + $item->add_to_rota($rota_id); + + } + +} + +1; + +=head1 AUTHOR + +Andrew Isherwood + +=cut diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index f3e843e6c3..c210c90959 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -25,5 +25,8 @@ }, "/illrequests": { "$ref": "paths/illrequests.json#/~1illrequests" + }, + "/rotas/{rota_id}/stages/{stage_id}/position": { + "$ref": "paths/rotas.json#/~1rotas~1{rota_id}~1stages~1{stage_id}~1position" } } diff --git a/api/v1/swagger/paths/rotas.json b/api/v1/swagger/paths/rotas.json new file mode 100644 index 0000000000..0cdda30317 --- /dev/null +++ b/api/v1/swagger/paths/rotas.json @@ -0,0 +1,79 @@ +{ + "/rotas/{rota_id}/stages/{stage_id}/position": { + "put": { + "x-mojo-to": "Stage#move", + "operationId": "moveStage", + "tags": ["rotas"], + "parameters": [{ + "name": "rota_id", + "in": "path", + "required": true, + "description": "A rotas ID", + "type": "integer" + }, { + "name": "stage_id", + "in": "path", + "required": true, + "description": "A stages ID", + "type": "integer" + }, { + "name": "position", + "in": "body", + "required": true, + "description": "A stages position in the rota", + "schema": { + "type": "integer" + } + }], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "401": { + "description": "Authentication required", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "403": { + "description": "Access forbidden", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "404": { + "description": "Position not found", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "../definitions.json#/error" + } + }, + "503": { + "description": "Under maintenance", + "schema": { + "$ref": "../definitions.json#/error" + } + } + }, + "x-koha-authorization": { + "permissions": { + "borrowers": "1" + } + } + } + } +} diff --git a/catalogue/stockrotation.pl b/catalogue/stockrotation.pl new file mode 100755 index 0000000000..ff63b8a270 --- /dev/null +++ b/catalogue/stockrotation.pl @@ -0,0 +1,175 @@ +#!/usr/bin/perl + +# Copyright 2016 PTFS-Europe Ltd +# +# 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. + +=head1 stockrotation.pl + + Script to manage item assignments to stock rotation rotas. Including their + assiciated stages + +=cut + +use Modern::Perl; +use CGI; + +use C4::Auth; +use C4::Output; +use C4::Search; + +use Koha::Biblio; +use Koha::Item; +use Koha::StockRotationRotas; +use Koha::StockRotationStages; +use Koha::Util::StockRotation qw(:ALL); + +my $input = new CGI; + +unless (C4::Context->preference('StockRotation')) { + # redirect to Intranet home if self-check is not enabled + print $input->redirect("/cgi-bin/koha/mainpage.pl"); + exit; +} + +my %params = $input->Vars(); + +my $op = $params{op}; + +my $biblionumber = $input->param('biblionumber'); + +my ($template, $loggedinuser, $cookie) = get_template_and_user( + { + template_name => 'catalogue/stockrotation.tt', + query => $input, + type => 'intranet', + authnotrequired => 0, + flagsrequired => { catalogue => 1 } + } +); + +if (!defined $op) { + + # List all items along with their associated rotas + my $biblio = Koha::Biblios->find($biblionumber); + + my $items = $biblio->items; + + # Get only rotas with stages + my $rotas = Koha::StockRotationRotas->search( + { + 'stockrotationstages.stage_id' => { '!=', undef } + }, + { + join => 'stockrotationstages', + collapse => 1 + } + ); + + # Construct a model to pass to the view + my @item_data = (); + + while (my $item = $items->next) { + + my $item_hashref = { + bib_item => $item + }; + + my $stockrotationitem = $item->stockrotationitem; + + # If this item is on a rota + if ($stockrotationitem != 0) { + + # This item's rota + my $rota = $stockrotationitem->stage->rota; + + # This rota's stages + my $stages = get_stages($rota); + + $item_hashref->{rota} = $rota; + + $item_hashref->{stockrotationitem} = $stockrotationitem; + + $item_hashref->{stages} = $stages; + + } + + push @item_data, $item_hashref; + + } + + $template->param( + no_op_set => 1, + rotas => $rotas, + items => \@item_data, + branches => get_branches(), + biblio => $biblio, + biblionumber => $biblio->biblionumber, + stockrotationview => 1, + C4::Search::enabled_staff_search_views + ); + +} elsif ($op eq "toggle_in_demand") { + + # Toggle in demand + toggle_indemand($params{item_id}, $params{stage_id}); + + # Return to items list + print $input->redirect("?biblionumber=$biblionumber"); + +} elsif ($op eq "remove_item_from_stage") { + + # Remove from the stage + remove_from_stage($params{item_id}, $params{stage_id}); + + # Return to items list + print $input->redirect("?biblionumber=$biblionumber"); + +} elsif ($op eq "move_to_next_stage") { + + move_to_next_stage($params{item_id}, $params{stage_id}); + + # Return to items list + print $input->redirect("?biblionumber=" . $params{biblionumber}); + +} elsif ($op eq "add_item_to_rota") { + + my $item = Koha::Items->find($params{item_id}); + + $item->add_to_rota($params{rota_id}); + + print $input->redirect("?biblionumber=" . $params{biblionumber}); + +} elsif ($op eq "confirm_remove_from_rota") { + + $template->param( + op => $params{op}, + stage_id => $params{stage_id}, + item_id => $params{item_id}, + biblionumber => $params{biblionumber}, + stockrotationview => 1, + C4::Search::enabled_staff_search_views + ); + +} + +output_html_with_http_headers $input, $cookie, $template->output; + +=head1 AUTHOR + +Andrew Isherwood + +=cut diff --git a/koha-tmpl/intranet-tmpl/prog/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/css/staff-global.css index 32a614bd70..c4ba0262f7 100644 --- a/koha-tmpl/intranet-tmpl/prog/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/css/staff-global.css @@ -3218,3 +3218,131 @@ span.name { font-weight: bold; font-style: italic; } + +#stockrotation h3 { + margin: 30px 0 10px 0; +} + +#stockrotation .dialog h3 { + margin: 10px 0; +} + +#stockrotation .dialog { + margin-bottom: 20px; +} + +#stockrotation .highlight_stage, +#catalog_stockrotation .highlight_stage { + font-weight: bold; +} + +#stockrotation #rota_form textarea { + width: 300px; + height: 100px; +} + +#stockrotation #rota_form #name { + width: 300px; +} + +#stockrotation #rota_form fieldset, +#stockrotation #stage_form fieldset, +#stockrotation #add_rota_item_form fieldset { + width: auto; +} + +#stockrotation .dialog.alert ul { + margin: 20px 0; +} + +#stockrotation .dialog.alert li { + list-style-type: none; +} + +#catalog_stockrotation .item_select_rota { + vertical-align: middle; +} + +#catalog_stockrotation h1 { + margin-bottom: 20px; +} + +#stockrotation td.actions, +#catalog_stockrotation td.actions { + vertical-align: middle; +} + +#stockrotation .stage, +#catalog_stockrotation .stage { + display: inline-block; + padding: 5px 7px; + margin: 3px 0 3px 0; + border-radius: 5px; + background-color: rgba(0,0,0,0.1); +} + +#stage_list_headings { + font-weight: bold; +} + +#stage_list_headings span { + padding: 3px; +} + +#manage_stages ul { + padding-left: 0; +} + +#manage_stages li { + list-style: none; + margin-bottom: 5px; +} + +#manage_stages li span { + padding: 6px 3px; +} + +#manage_stages .stagename { + width: 15em; + display: inline-block; +} + +#manage_stages .stageduration { + width: 10em; + display: inline-block; +} + +#manage_stages .stageactions { + display: inline-block; +} + +#manage_stages li:nth-child(odd) { + background-color : #F3F3F3; +} + +#manage_stages .drag_handle { + margin-right: 6px; + cursor: move; +} + +#manage_stages .drag_placeholder { + height: 2em; + border: 1px dotted #aaa; +} + +#manage_stages h3 { + display: inline-block; +} + +#manage_stages #ajax_status { + display: inline-block; + border: 1px solid #bcbcbc; + border-radius: 5px; + padding: 5px; + margin-left: 10px; + background: #f3f3f3; +} + +#manage_stages #manage_stages_help { + margin: 20px 0; +} diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc index 95c0ee4640..b32f60939e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/biblio-view-menu.inc @@ -40,6 +40,7 @@ [% IF ( issuehistoryview ) %]
  • [% ELSE %]
  • [% END %] Checkout history
  • [% IF ( CAN_user_tools_view_system_logs ) %][% IF ( logview ) %]
  • [% ELSE %]
  • [% END %]Modification log
  • [% END %] +[% IF ( CAN_user_stockrotation_can_add_items_rotas && Koha.Preference('StockRotation') ) %][% IF ( stockrotationview ) %]
  • [% ELSE %]
  • [% END %]Rota
  • [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/stockrotation-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/stockrotation-toolbar.inc new file mode 100644 index 0000000000..c8e72e9dd6 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/stockrotation-toolbar.inc @@ -0,0 +1,13 @@ +[% USE Koha %] +
    + [% IF no_op_set %] + New rota + [% END %] + [% IF op == 'manage_stages' %] + New stage + [% END %] + [% IF op == 'manage_items' %] + New rota + Edit rota + [% END %] +
    diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc index 50e52f73c0..d4a6ad2b1c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -1,3 +1,22 @@ +[% USE Koha %] + +