Kyle M Hall
b2b1483f4d
Some of our partners have unusual barcode requirements that have required us to transform scanned barcodes using javascript. This is not the most reliable method. It would make more sense to have Koha transform the barcodes on the backend using a plugin. We should add hooks to transform and generate new item and patron barcodes. Test Plan: 1) Apply this patch 2) Download and install the Barcode Transformer plugin https://github.com/bywatersolutions/koha-plugin-barcode-transformer/releases/ 3) Go to the plugin configuration page, set the configuration to the example configuration from the same page 4) In the item barcode field on the checkin and checkout pages, and anywhere else you can scan an item barcode, type in some valid barcodes, but prefix them with X and postfix them with Y, e.g. X123456Y 5) Note the letters are removed by Koha! Signed-off-by: Lucas Gass <lucas@bywatersolutions.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Fix QA script issue * Fixes issue with barcode generate stub so perlcritic is happy * Removes extra semicolon from return call in configure method Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: Add unit tests Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Remove unused method barcode_transform Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Rename barcode_transform to item_barcode_transform Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Barcodes inputted into Koha should always pass though barcodedecode Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Catch one last case of itemBarcodeInputFilter Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: (QA follow-up) Fix Checkouts.t Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: Use call_recursive() as a replacement for call() The method `call()` is not sufficient for barcode transformations. It's possible that more than one barcode transformation plugin will be installed. The `call_recursive()` method takes the output of the first plugin and uses it as the input for the next plugin and so on. This allowes each plugin to see the current version of the barcode and modify it if necessary. Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Bug 26351: Fix t/db_dependent/Koha/Plugins/Circulation_hooks.t Bug 26351: Revert improper change to unit test, fix number of tests Bug 26351: Remove uneeded use Koha::Plugins statements Left over from previous changes Bug 26351: Add missing barcodedecode import Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
762 lines
32 KiB
Perl
Executable file
762 lines
32 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
|
|
|
|
# Copyright 2000-2002 Katipo Communications
|
|
#
|
|
# 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, see <http://www.gnu.org/licenses>.
|
|
|
|
use CGI qw ( -utf8 );
|
|
use Modern::Perl;
|
|
use Try::Tiny qw( catch try );
|
|
|
|
use C4::Auth qw( get_template_and_user haspermission );
|
|
use C4::Output qw( output_html_with_http_headers );
|
|
use C4::Biblio qw(
|
|
DelBiblio
|
|
GetAuthorisedValueDesc
|
|
GetMarcFromKohaField
|
|
GetMarcStructure
|
|
IsMarcStructureInternal
|
|
TransformHtmlToXml
|
|
);
|
|
use C4::Items qw( GetItemsInfo Item2Marc ModItemFromMarc );
|
|
use C4::Circulation qw( barcodedecode LostItem IsItemIssued );
|
|
use C4::Context;
|
|
use C4::Koha;
|
|
use C4::BackgroundJob;
|
|
use C4::ClassSource qw( GetClassSources GetClassSource );
|
|
use MARC::File::XML;
|
|
use List::MoreUtils qw( uniq );
|
|
|
|
use Koha::Database;
|
|
use Koha::Exceptions::Exception;
|
|
use Koha::AuthorisedValues;
|
|
use Koha::Biblios;
|
|
use Koha::DateUtils qw( dt_from_string );
|
|
use Koha::Items;
|
|
use Koha::ItemTypes;
|
|
use Koha::Patrons;
|
|
use Koha::SearchEngine::Indexer;
|
|
|
|
my $input = CGI->new;
|
|
my $dbh = C4::Context->dbh;
|
|
my $error = $input->param('error');
|
|
my @itemnumbers = $input->multi_param('itemnumber');
|
|
my $biblionumber = $input->param('biblionumber');
|
|
my $op = $input->param('op');
|
|
my $del = $input->param('del');
|
|
my $del_records = $input->param('del_records');
|
|
my $src = $input->param('src');
|
|
my $use_default_values = $input->param('use_default_values');
|
|
my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority');
|
|
|
|
my $template_name;
|
|
my $template_flag;
|
|
if (!defined $op) {
|
|
$template_name = "tools/batchMod.tt";
|
|
$template_flag = { tools => '*' };
|
|
$op = q{};
|
|
} else {
|
|
$template_name = ($del) ? "tools/batchMod-del.tt" : "tools/batchMod-edit.tt";
|
|
$template_flag = ($del) ? { tools => 'items_batchdel' } : { tools => 'items_batchmod' };
|
|
}
|
|
|
|
my ($template, $loggedinuser, $cookie)
|
|
= get_template_and_user({template_name => $template_name,
|
|
query => $input,
|
|
type => "intranet",
|
|
flagsrequired => $template_flag,
|
|
});
|
|
|
|
$template->param( searchid => scalar $input->param('searchid'), );
|
|
|
|
# Does the user have a restricted item edition permission?
|
|
my $uid = $loggedinuser ? Koha::Patrons->find( $loggedinuser )->userid : undef;
|
|
my $restrictededition = $uid ? haspermission($uid, {'tools' => 'items_batchmod_restricted'}) : undef;
|
|
# In case user is a superlibrarian, edition is not restricted
|
|
$restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
|
|
|
|
$template->param(del => $del);
|
|
|
|
my $nextop="";
|
|
my @errors; # store errors found while checking data BEFORE saving item.
|
|
my $items_display_hashref;
|
|
our $tagslib = &GetMarcStructure(1);
|
|
|
|
my $deleted_items = 0; # Number of deleted items
|
|
my $deleted_records = 0; # Number of deleted records ( with no items attached )
|
|
my $not_deleted_items = 0; # Number of items that could not be deleted
|
|
my @not_deleted; # List of the itemnumbers that could not be deleted
|
|
my $modified_items = 0; # Numbers of modified items
|
|
my $modified_fields = 0; # Numbers of modified fields
|
|
|
|
my %cookies = parse CGI::Cookie($cookie);
|
|
my $sessionID = $cookies{'CGISESSID'}->value;
|
|
|
|
|
|
#--- ----------------------------------------------------------------------------
|
|
if ($op eq "action") {
|
|
#-------------------------------------------------------------------------------
|
|
my @tags = $input->multi_param('tag');
|
|
my @subfields = $input->multi_param('subfield');
|
|
my @values = $input->multi_param('field_value');
|
|
my @searches = $input->multi_param('regex_search');
|
|
my @replaces = $input->multi_param('regex_replace');
|
|
my @modifiers = $input->multi_param('regex_modifiers');
|
|
my @disabled = $input->multi_param('disable_input');
|
|
# build indicator hash.
|
|
my @ind_tag = $input->multi_param('ind_tag');
|
|
my @indicator = $input->multi_param('indicator');
|
|
|
|
# Is there something to modify ?
|
|
# TODO : We shall use this var to warn the user in case no modification was done to the items
|
|
my $values_to_modify = scalar(grep {!/^$/} @values) || scalar(grep {!/^$/} @searches);
|
|
my $values_to_blank = scalar(@disabled);
|
|
|
|
my $marcitem;
|
|
|
|
#initializing values for updates
|
|
my ( $itemtagfield, $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
|
|
if ($values_to_modify){
|
|
my $xml = TransformHtmlToXml(\@tags,\@subfields,\@values,\@indicator,\@ind_tag, 'ITEM');
|
|
$marcitem = MARC::Record::new_from_xml($xml, 'UTF-8');
|
|
}
|
|
if ($values_to_blank){
|
|
foreach my $disabledsubf (@disabled){
|
|
if ($marcitem && $marcitem->field($itemtagfield)){
|
|
$marcitem->field($itemtagfield)->update( $disabledsubf => "" );
|
|
}
|
|
else {
|
|
$marcitem = MARC::Record->new();
|
|
$marcitem->append_fields( MARC::Field->new( $itemtagfield, '', '', $disabledsubf => "" ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
my $upd_biblionumbers;
|
|
my $del_biblionumbers;
|
|
try {
|
|
my $schema = Koha::Database->new->schema;
|
|
$schema->txn_do(
|
|
sub {
|
|
# For each item
|
|
my $i = 1;
|
|
foreach my $itemnumber (@itemnumbers) {
|
|
my $item = Koha::Items->find($itemnumber);
|
|
next
|
|
unless $item
|
|
; # Should have been tested earlier, but just in case...
|
|
my $itemdata = $item->unblessed;
|
|
if ($del) {
|
|
my $return = $item->safe_delete;
|
|
if ( ref( $return ) ) {
|
|
$deleted_items++;
|
|
push @$upd_biblionumbers, $itemdata->{'biblionumber'};
|
|
}
|
|
else {
|
|
$not_deleted_items++;
|
|
push @not_deleted,
|
|
{
|
|
biblionumber => $itemdata->{'biblionumber'},
|
|
itemnumber => $itemdata->{'itemnumber'},
|
|
barcode => $itemdata->{'barcode'},
|
|
title => $itemdata->{'title'},
|
|
reason => $return,
|
|
};
|
|
}
|
|
|
|
# If there are no items left, delete the biblio
|
|
if ($del_records) {
|
|
my $itemscount = Koha::Biblios->find( $itemdata->{'biblionumber'} )->items->count;
|
|
if ( $itemscount == 0 ) {
|
|
my $error = DelBiblio( $itemdata->{'biblionumber'}, { skip_record_index => 1 } );
|
|
unless ($error) {
|
|
$deleted_records++;
|
|
push @$del_biblionumbers, $itemdata->{'biblionumber'};
|
|
if ( $src eq 'CATALOGUING' ) {
|
|
# We are coming catalogue/detail.pl, there were items from a single bib record
|
|
$template->param( biblio_deleted => 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
my $modified_holds_priority = 0;
|
|
if ( defined $exclude_from_local_holds_priority && $exclude_from_local_holds_priority ne "" ) {
|
|
if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
|
|
$item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
|
|
$modified_holds_priority = 1;
|
|
}
|
|
}
|
|
my $modified = 0;
|
|
if ( $values_to_modify || $values_to_blank ) {
|
|
my $localmarcitem = Item2Marc($itemdata);
|
|
|
|
for ( my $i = 0 ; $i < @tags ; $i++ ) {
|
|
my $search = $searches[$i];
|
|
next unless $search;
|
|
|
|
my $tag = $tags[$i];
|
|
my $subfield = $subfields[$i];
|
|
my $replace = $replaces[$i];
|
|
|
|
my $value = $localmarcitem->field( $tag )->subfield( $subfield );
|
|
my $old_value = $value;
|
|
|
|
my @available_modifiers = qw( i g );
|
|
my $retained_modifiers = q||;
|
|
for my $modifier ( split //, $modifiers[$i] ) {
|
|
$retained_modifiers .= $modifier
|
|
if grep {/$modifier/} @available_modifiers;
|
|
}
|
|
if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
|
|
$value =~ s/$search/$replace/ig;
|
|
}
|
|
elsif ( $retained_modifiers eq 'i' ) {
|
|
$value =~ s/$search/$replace/i;
|
|
}
|
|
elsif ( $retained_modifiers eq 'g' ) {
|
|
$value =~ s/$search/$replace/g;
|
|
}
|
|
else {
|
|
$value =~ s/$search/$replace/;
|
|
}
|
|
|
|
my @fields_to = $localmarcitem->field($tag);
|
|
foreach my $field_to_update ( @fields_to ) {
|
|
unless ( $old_value eq $value ) {
|
|
$modified++;
|
|
$field_to_update->update( $subfield => $value );
|
|
}
|
|
}
|
|
}
|
|
|
|
$modified += UpdateMarcWith( $marcitem, $localmarcitem );
|
|
if ($modified) {
|
|
eval {
|
|
if (
|
|
my $item = ModItemFromMarc(
|
|
$localmarcitem,
|
|
$itemdata->{biblionumber},
|
|
$itemnumber,
|
|
{ skip_record_index => 1 },
|
|
)
|
|
)
|
|
{
|
|
LostItem(
|
|
$itemnumber,
|
|
'batchmod',
|
|
undef,
|
|
{ skip_record_index => 1 }
|
|
) if $item->{itemlost}
|
|
and not $itemdata->{itemlost};
|
|
}
|
|
};
|
|
push @$upd_biblionumbers, $itemdata->{'biblionumber'};
|
|
}
|
|
}
|
|
$modified_items++ if $modified || $modified_holds_priority;
|
|
$modified_fields += $modified + $modified_holds_priority;
|
|
}
|
|
$i++;
|
|
}
|
|
if (@not_deleted) {
|
|
Koha::Exceptions::Exception->throw(
|
|
'Some items have not been deleted, rolling back');
|
|
}
|
|
}
|
|
);
|
|
}
|
|
catch {
|
|
if ( $_->isa('Koha::Exceptions::Exception') ) {
|
|
$template->param( deletion_failed => 1 );
|
|
}
|
|
die "Something terrible has happened!"
|
|
if ($_ =~ /Rollback failed/); # Rollback failed
|
|
};
|
|
$upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once
|
|
|
|
# Don't send specialUpdate for records we are going to delete
|
|
my %del_bib_hash = map{ $_ => undef } @$del_biblionumbers;
|
|
@$upd_biblionumbers = grep( ! exists( $del_bib_hash{$_} ), @$upd_biblionumbers );
|
|
|
|
my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
|
|
$indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
|
|
$indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
|
|
|
|
# Once the job is done
|
|
# If we have a reasonable amount of items, we display them
|
|
my $max_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") : C4::Context->preference("MaxItemsToDisplayForBatchMod");
|
|
if (scalar(@itemnumbers) <= $max_items ){
|
|
if (scalar(@itemnumbers) <= 1000 ) {
|
|
$items_display_hashref=BuildItemsData(@itemnumbers);
|
|
} else {
|
|
# Else, we only display the barcode
|
|
my @simple_items_display = map {
|
|
my $itemnumber = $_;
|
|
my $item = Koha::Items->find($itemnumber);
|
|
{
|
|
itemnumber => $itemnumber,
|
|
barcode => $item ? ( $item->barcode // q{} ) : q{},
|
|
biblionumber => $item ? $item->biblio->biblionumber : q{},
|
|
};
|
|
} @itemnumbers;
|
|
$template->param("simple_items_display" => \@simple_items_display);
|
|
}
|
|
} else {
|
|
$template->param( "too_many_items_display" => scalar(@itemnumbers) );
|
|
$template->param( "job_completed" => 1 );
|
|
}
|
|
|
|
# Calling the template
|
|
$template->param(
|
|
modified_items => $modified_items,
|
|
modified_fields => $modified_fields,
|
|
);
|
|
|
|
}
|
|
#
|
|
#-------------------------------------------------------------------------------
|
|
# build screen with existing items. and "new" one
|
|
#-------------------------------------------------------------------------------
|
|
|
|
if ($op eq "show"){
|
|
my $filefh = $input->upload('uploadfile');
|
|
my $filecontent = $input->param('filecontent');
|
|
my ( @notfoundbarcodes, @notfounditemnumbers);
|
|
|
|
my $split_chars = C4::Context->preference('BarcodeSeparators');
|
|
if ($filefh){
|
|
binmode $filefh, ':encoding(UTF-8)';
|
|
my @contentlist;
|
|
while (my $content=<$filefh>){
|
|
$content =~ s/[\r\n]*$//;
|
|
push @contentlist, $content if $content;
|
|
}
|
|
|
|
if ($filecontent eq 'barcode_file') {
|
|
@contentlist = grep /\S/, ( map { split /[$split_chars]/ } @contentlist );
|
|
@contentlist = uniq @contentlist;
|
|
# Note: adding lc for case insensitivity
|
|
my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@contentlist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed };
|
|
@itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @contentlist;
|
|
@notfoundbarcodes = grep { !exists $itemdata{lc $_} } @contentlist;
|
|
}
|
|
elsif ( $filecontent eq 'itemid_file') {
|
|
@contentlist = uniq @contentlist;
|
|
my %itemdata = map { $_->{itemnumber} => 1 } @{ Koha::Items->search({ itemnumber => \@contentlist }, { columns => [ 'itemnumber' ] } )->unblessed };
|
|
@itemnumbers = grep { exists $itemdata{$_} } @contentlist;
|
|
@notfounditemnumbers = grep { !exists $itemdata{$_} } @contentlist;
|
|
}
|
|
} else {
|
|
if (defined $biblionumber && !@itemnumbers){
|
|
my @all_items = GetItemsInfo( $biblionumber );
|
|
foreach my $itm (@all_items) {
|
|
push @itemnumbers, $itm->{itemnumber};
|
|
}
|
|
}
|
|
if ( my $list = $input->param('barcodelist') ) {
|
|
my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
|
|
@barcodelist = uniq @barcodelist;
|
|
|
|
@barcodelist = map { barcodedecode( $_ ) } @barcodelist;
|
|
|
|
# Note: adding lc for case insensitivity
|
|
my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@barcodelist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed };
|
|
@itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @barcodelist;
|
|
@notfoundbarcodes = grep { !exists $itemdata{lc $_} } @barcodelist;
|
|
}
|
|
}
|
|
|
|
# Flag to tell the template there are valid results, hidden or not
|
|
if(scalar(@itemnumbers) > 0){ $template->param("itemresults" => 1); }
|
|
# Only display the items if there are no more than pref MaxItemsToProcessForBatchMod or MaxItemsToDisplayForBatchDel
|
|
my $max_display_items = $del
|
|
? C4::Context->preference("MaxItemsToDisplayForBatchDel")
|
|
: C4::Context->preference("MaxItemsToDisplayForBatchMod");
|
|
$template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
|
|
if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
|
|
$items_display_hashref=BuildItemsData(@itemnumbers);
|
|
} else {
|
|
$template->param("too_many_items_display" => scalar(@itemnumbers));
|
|
# Even if we do not display the items, we need the itemnumbers
|
|
$template->param(itemnumbers_array => \@itemnumbers);
|
|
}
|
|
# now, build the item form for entering a new item
|
|
my @loop_data =();
|
|
my $i=0;
|
|
my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
|
|
|
|
my $libraries = Koha::Libraries->search({}, { order_by => ['branchname'] })->unblessed;# build once ahead of time, instead of multiple times later.
|
|
|
|
# Adding a default choice, in case the user does not want to modify the branch
|
|
my $nochange_branch = { branchname => '', value => '', selected => 1 };
|
|
unshift (@$libraries, $nochange_branch);
|
|
|
|
my $pref_itemcallnumber = C4::Context->preference('itemcallnumber');
|
|
|
|
# Getting list of subfields to keep when restricted batchmod edit is enabled
|
|
my $subfieldsToAllowForBatchmod = C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod');
|
|
my $allowAllSubfields = (
|
|
not defined $subfieldsToAllowForBatchmod
|
|
or $subfieldsToAllowForBatchmod eq q||
|
|
) ? 1 : 0;
|
|
my @subfieldsToAllow = split(/ /, $subfieldsToAllowForBatchmod);
|
|
|
|
foreach my $tag (sort keys %{$tagslib}) {
|
|
# loop through each subfield
|
|
foreach my $subfield (sort keys %{$tagslib->{$tag}}) {
|
|
next if IsMarcStructureInternal( $tagslib->{$tag}{$subfield} );
|
|
next if (not $allowAllSubfields and $restrictededition && !grep { $tag . '$' . $subfield eq $_ } @subfieldsToAllow );
|
|
next if ($tagslib->{$tag}->{$subfield}->{'tab'} ne "10");
|
|
# barcode is not meant to be batch-modified
|
|
next if $tagslib->{$tag}->{$subfield}->{'kohafield'} eq 'items.barcode';
|
|
my %subfield_data;
|
|
|
|
my $index_subfield = int(rand(1000000));
|
|
if ($subfield eq '@'){
|
|
$subfield_data{id} = "tag_".$tag."_subfield_00_".$index_subfield;
|
|
} else {
|
|
$subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".$index_subfield;
|
|
}
|
|
$subfield_data{tag} = $tag;
|
|
$subfield_data{subfield} = $subfield;
|
|
$subfield_data{marc_lib} ="<span id=\"error$i\" title=\"".$tagslib->{$tag}->{$subfield}->{lib}."\">".$tagslib->{$tag}->{$subfield}->{lib}."</span>";
|
|
$subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
|
|
$subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
|
|
my $value;
|
|
if ( $use_default_values) {
|
|
$value = $tagslib->{$tag}->{$subfield}->{defaultvalue};
|
|
# get today date & replace YYYY, MM, DD if provided in the default value
|
|
my $today = dt_from_string;
|
|
my $year = $today->year;
|
|
my $month = $today->month;
|
|
my $day = $today->day;
|
|
$value =~ s/YYYY/$year/g;
|
|
$value =~ s/MM/$month/g;
|
|
$value =~ s/DD/$day/g;
|
|
}
|
|
$subfield_data{visibility} = "display:none;" if (($tagslib->{$tag}->{$subfield}->{hidden} > 4) || ($tagslib->{$tag}->{$subfield}->{hidden} < -4));
|
|
# testing branch value if IndependentBranches.
|
|
|
|
if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
|
|
my @authorised_values;
|
|
my %authorised_lib;
|
|
# builds list, depending on authorised value...
|
|
|
|
if ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "branches" ) {
|
|
foreach my $library (@$libraries) {
|
|
push @authorised_values, $library->{branchcode};
|
|
$authorised_lib{$library->{branchcode}} = $library->{branchname};
|
|
}
|
|
$value = "";
|
|
}
|
|
elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
|
|
push @authorised_values, "";
|
|
my $itemtypes = Koha::ItemTypes->search_with_localization;
|
|
while ( my $itemtype = $itemtypes->next ) {
|
|
push @authorised_values, $itemtype->itemtype;
|
|
$authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
|
|
}
|
|
$value = "";
|
|
|
|
#---- class_sources
|
|
}
|
|
elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
|
|
push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
|
|
|
|
my $class_sources = GetClassSources();
|
|
my $default_source = C4::Context->preference("DefaultClassificationSource");
|
|
|
|
foreach my $class_source (sort keys %$class_sources) {
|
|
next unless $class_sources->{$class_source}->{'used'} or
|
|
($value and $class_source eq $value) or
|
|
($class_source eq $default_source);
|
|
push @authorised_values, $class_source;
|
|
$authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
|
|
}
|
|
$value = '';
|
|
|
|
#---- "true" authorised value
|
|
}
|
|
else {
|
|
push @authorised_values, ""; # unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
|
|
|
|
my @avs = Koha::AuthorisedValues->search_with_library_limits(
|
|
{
|
|
category => $tagslib->{$tag}->{$subfield}->{authorised_value}
|
|
},
|
|
{ order_by => 'lib' },
|
|
$branch_limit
|
|
);
|
|
for my $av ( @avs ) {
|
|
push @authorised_values, $av->authorised_value;
|
|
$authorised_lib{$av->authorised_value} = $av->lib;
|
|
}
|
|
$value="";
|
|
}
|
|
$subfield_data{marc_value} = {
|
|
type => 'select',
|
|
id => "tag_".$tag."_subfield_".$subfield."_".$index_subfield,
|
|
name => "field_value",
|
|
values => \@authorised_values,
|
|
labels => \%authorised_lib,
|
|
default => $value,
|
|
};
|
|
# it's a thesaurus / authority field
|
|
}
|
|
elsif ( $tagslib->{$tag}->{$subfield}->{authtypecode} ) {
|
|
$subfield_data{marc_value} = {
|
|
type => 'text1',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
authtypecode => $tagslib->{$tag}->{$subfield}->{authtypecode},
|
|
}
|
|
}
|
|
elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) { # plugin
|
|
require Koha::FrameworkPlugin;
|
|
my $plugin = Koha::FrameworkPlugin->new( {
|
|
name => $tagslib->{$tag}->{$subfield}->{'value_builder'},
|
|
item_style => 1,
|
|
});
|
|
my $temp;
|
|
my $pars= { dbh => $dbh, record => $temp, tagslib => $tagslib,
|
|
id => $subfield_data{id}, tabloop => \@loop_data };
|
|
$plugin->build( $pars );
|
|
if( !$plugin->errstr ) {
|
|
$subfield_data{marc_value} = {
|
|
type => 'text2',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
javascript => $plugin->javascript,
|
|
noclick => $plugin->noclick,
|
|
};
|
|
} else {
|
|
warn $plugin->errstr;
|
|
$subfield_data{marc_value} = { # supply default input form
|
|
type => 'text',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
};
|
|
}
|
|
}
|
|
elsif ( $tag eq '' ) { # it's an hidden field
|
|
$subfield_data{marc_value} = {
|
|
type => 'hidden',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
};
|
|
}
|
|
elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
|
|
$subfield_data{marc_value} = {
|
|
type => 'text',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
};
|
|
}
|
|
elsif ( length($value) > 100
|
|
or (C4::Context->preference("marcflavour") eq "UNIMARC" and
|
|
300 <= $tag && $tag < 400 && $subfield eq 'a' )
|
|
or (C4::Context->preference("marcflavour") eq "MARC21" and
|
|
500 <= $tag && $tag < 600 )
|
|
) {
|
|
# oversize field (textarea)
|
|
$subfield_data{marc_value} = {
|
|
type => 'textarea',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
};
|
|
} else {
|
|
# it's a standard field
|
|
$subfield_data{marc_value} = {
|
|
type => 'text',
|
|
id => $subfield_data{id},
|
|
value => $value,
|
|
};
|
|
}
|
|
# $subfield_data{marc_value}="<input type=\"text\" name=\"field_value\">";
|
|
push (@loop_data, \%subfield_data);
|
|
$i++
|
|
}
|
|
} # -- End foreach tag
|
|
|
|
|
|
|
|
# what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit.
|
|
$template->param(
|
|
item => \@loop_data,
|
|
notfoundbarcodes => \@notfoundbarcodes,
|
|
notfounditemnumbers => \@notfounditemnumbers
|
|
);
|
|
$nextop="action"
|
|
} # -- End action="show"
|
|
|
|
$template->param(%$items_display_hashref) if $items_display_hashref;
|
|
$template->param(
|
|
op => $nextop,
|
|
);
|
|
$template->param( $op => 1 ) if $op;
|
|
|
|
if ($op eq "action") {
|
|
|
|
#my @not_deleted_loop = map{{itemnumber=>$_}}@not_deleted;
|
|
|
|
$template->param(
|
|
not_deleted_items => $not_deleted_items,
|
|
deleted_items => $deleted_items,
|
|
delete_records => $del_records,
|
|
deleted_records => $deleted_records,
|
|
not_deleted_loop => \@not_deleted
|
|
);
|
|
}
|
|
|
|
foreach my $error (@errors) {
|
|
$template->param($error => 1) if $error;
|
|
}
|
|
$template->param(src => $src);
|
|
$template->param(biblionumber => $biblionumber);
|
|
output_html_with_http_headers $input, $cookie, $template->output;
|
|
exit;
|
|
|
|
|
|
# ---------------- Functions
|
|
|
|
sub BuildItemsData{
|
|
my @itemnumbers=@_;
|
|
# now, build existiing item list
|
|
my %witness; #---- stores the list of subfields used at least once, with the "meaning" of the code
|
|
my @big_array;
|
|
#---- finds where items.itemnumber is stored
|
|
my ( $itemtagfield, $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
|
|
my ($branchtagfield, $branchtagsubfield) = &GetMarcFromKohaField( "items.homebranch" );
|
|
foreach my $itemnumber (@itemnumbers){
|
|
my $itemdata = Koha::Items->find($itemnumber);
|
|
next unless $itemdata; # Should have been tested earlier, but just in case...
|
|
$itemdata = $itemdata->unblessed;
|
|
my $itemmarc=Item2Marc($itemdata);
|
|
my %this_row;
|
|
foreach my $field (grep {$_->tag() eq $itemtagfield} $itemmarc->fields()) {
|
|
# loop through each subfield
|
|
my $itembranchcode=$field->subfield($branchtagsubfield);
|
|
if ($itembranchcode && C4::Context->preference("IndependentBranches")) {
|
|
#verifying rights
|
|
my $userenv = C4::Context->userenv();
|
|
unless (C4::Context->IsSuperLibrarian() or (($userenv->{'branch'} eq $itembranchcode))){
|
|
$this_row{'nomod'}=1;
|
|
}
|
|
}
|
|
my $tag=$field->tag();
|
|
foreach my $subfield ($field->subfields) {
|
|
my ($subfcode,$subfvalue)=@$subfield;
|
|
next if ($tagslib->{$tag}->{$subfcode}->{tab} ne 10
|
|
&& $tag ne $itemtagfield
|
|
&& $subfcode ne $itemtagsubfield);
|
|
|
|
$witness{$subfcode} = $tagslib->{$tag}->{$subfcode}->{lib} if ($tagslib->{$tag}->{$subfcode}->{tab} eq 10);
|
|
if ($tagslib->{$tag}->{$subfcode}->{tab} eq 10) {
|
|
$this_row{$subfcode}=GetAuthorisedValueDesc( $tag,
|
|
$subfcode, $subfvalue, '', $tagslib)
|
|
|| $subfvalue;
|
|
}
|
|
|
|
$this_row{itemnumber} = $subfvalue if ($tag eq $itemtagfield && $subfcode eq $itemtagsubfield);
|
|
}
|
|
}
|
|
|
|
# grab title, author, and ISBN to identify bib that the item
|
|
# belongs to in the display
|
|
my $biblio = Koha::Biblios->find( $itemdata->{biblionumber} );
|
|
$this_row{title} = $biblio->title;
|
|
$this_row{author} = $biblio->author;
|
|
$this_row{isbn} = $biblio->biblioitem->isbn;
|
|
$this_row{biblionumber} = $biblio->biblionumber;
|
|
$this_row{holds} = $biblio->holds->count;
|
|
$this_row{item_holds} = Koha::Holds->search( { itemnumber => $itemnumber } )->count;
|
|
$this_row{item} = Koha::Items->find($itemnumber);
|
|
|
|
if (%this_row) {
|
|
push(@big_array, \%this_row);
|
|
}
|
|
}
|
|
@big_array = sort {$a->{0} cmp $b->{0}} @big_array;
|
|
|
|
# now, construct template !
|
|
# First, the existing items for display
|
|
my @item_value_loop;
|
|
my @witnesscodessorted=sort keys %witness;
|
|
for my $row ( @big_array ) {
|
|
my %row_data;
|
|
my @item_fields = map +{ field => $_ || '' }, @$row{ @witnesscodessorted };
|
|
$row_data{item_value} = [ @item_fields ];
|
|
$row_data{itemnumber} = $row->{itemnumber};
|
|
#reporting this_row values
|
|
$row_data{'nomod'} = $row->{'nomod'};
|
|
$row_data{bibinfo} = $row->{bibinfo};
|
|
$row_data{author} = $row->{author};
|
|
$row_data{title} = $row->{title};
|
|
$row_data{isbn} = $row->{isbn};
|
|
$row_data{biblionumber} = $row->{biblionumber};
|
|
$row_data{holds} = $row->{holds};
|
|
$row_data{item_holds} = $row->{item_holds};
|
|
$row_data{item} = $row->{item};
|
|
$row_data{safe_to_delete} = $row->{item}->safe_to_delete;
|
|
my $is_on_loan = C4::Circulation::IsItemIssued( $row->{itemnumber} );
|
|
$row_data{onloan} = $is_on_loan ? 1 : 0;
|
|
push(@item_value_loop,\%row_data);
|
|
}
|
|
my @header_loop=map { { header_value=> $witness{$_}} } @witnesscodessorted;
|
|
|
|
my @cannot_be_deleted = map {
|
|
$_->{safe_to_delete} == 1 ? () : $_->{item}->barcode
|
|
} @item_value_loop;
|
|
return {
|
|
item_loop => \@item_value_loop,
|
|
cannot_be_deleted => \@cannot_be_deleted,
|
|
item_header_loop => \@header_loop
|
|
};
|
|
}
|
|
|
|
#BE WARN : it is not the general case
|
|
# This function can be OK in the item marc record special case
|
|
# Where subfield is not repeated
|
|
# And where we are sure that field should correspond
|
|
# And $tag>10
|
|
sub UpdateMarcWith {
|
|
my ($marcfrom,$marcto)=@_;
|
|
my ( $itemtag, $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
|
|
my $fieldfrom=$marcfrom->field($itemtag);
|
|
my @fields_to=$marcto->field($itemtag);
|
|
my $modified = 0;
|
|
|
|
return $modified unless $fieldfrom;
|
|
|
|
foreach my $subfield ( $fieldfrom->subfields() ) {
|
|
foreach my $field_to_update ( @fields_to ) {
|
|
if ( $subfield->[1] ) {
|
|
unless ( $field_to_update->subfield($subfield->[0]) eq $subfield->[1] ) {
|
|
$modified++;
|
|
$field_to_update->update( $subfield->[0] => $subfield->[1] );
|
|
}
|
|
}
|
|
else {
|
|
$modified++;
|
|
$field_to_update->delete_subfield( code => $subfield->[0] );
|
|
}
|
|
}
|
|
}
|
|
return $modified;
|
|
}
|