Bug 22785: Allow option to choose which record match is applied during import
[koha.git] / tools / batchMod.pl
1 #!/usr/bin/perl
2
3
4 # Copyright 2000-2002 Katipo Communications
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use CGI qw ( -utf8 );
22 use Modern::Perl;
23 use Try::Tiny qw( catch try );
24
25 use C4::Auth qw( get_template_and_user haspermission );
26 use C4::Output qw( output_html_with_http_headers );
27 use C4::Circulation qw( barcodedecode );
28 use C4::Context;
29 use MARC::File::XML;
30 use List::MoreUtils qw( uniq );
31 use Encode qw( encode_utf8 );
32
33 use Koha::Database;
34 use Koha::DateUtils qw( dt_from_string );
35 use Koha::Exception;
36 use Koha::Biblios;
37 use Koha::Items;
38 use Koha::Patrons;
39 use Koha::Item::Attributes;
40 use Koha::BackgroundJob::BatchDeleteItem;
41 use Koha::BackgroundJob::BatchUpdateItem;
42 use Koha::UI::Form::Builder::Item;
43 use Koha::UI::Table::Builder::Items;
44
45 my $input = CGI->new;
46 my $dbh = C4::Context->dbh;
47 my $error        = $input->param('error');
48 my @itemnumbers  = $input->multi_param('itemnumber');
49 my $biblionumber = $input->param('biblionumber');
50 my $op           = $input->param('op');
51 my $del          = $input->param('del');
52 my $del_records  = $input->param('del_records');
53 my $src          = $input->param('src');
54 my $use_default_values = $input->param('use_default_values');
55 my $exclude_from_local_holds_priority = $input->param('exclude_from_local_holds_priority');
56
57 my $template_name;
58 my $template_flag;
59 if (!defined $op) {
60     $template_name = "tools/batchMod.tt";
61     $template_flag = { tools => '*' };
62     $op = q{};
63 } else {
64     $template_name = ($del) ? "tools/batchMod-del.tt" : "tools/batchMod-edit.tt";
65     $template_flag = ($del) ? { tools => 'items_batchdel' }   : { tools => 'items_batchmod' };
66 }
67
68 my ($template, $loggedinuser, $cookie)
69     = get_template_and_user({template_name => $template_name,
70                  query => $input,
71                  type => "intranet",
72                  flagsrequired => $template_flag,
73                  });
74
75 $template->param( searchid => scalar $input->param('searchid'), );
76
77 # Does the user have a restricted item edition permission?
78 my $uid = $loggedinuser ? Koha::Patrons->find( $loggedinuser )->userid : undef;
79 my $restrictededition = $uid ? haspermission($uid,  {'tools' => 'items_batchmod_restricted'}) : undef;
80 # In case user is a superlibrarian, edition is not restricted
81 $restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
82
83 my $nextop="";
84 my $display_items;
85
86 my @messages;
87
88 if ( $op eq "action" ) {
89
90     if ($del) {
91         try {
92             my $params = {
93                 record_ids     => \@itemnumbers,
94                 delete_biblios => $del_records,
95             };
96             my $job_id =
97               Koha::BackgroundJob::BatchDeleteItem->new->enqueue($params);
98             $nextop = 'enqueued';
99             $template->param( job_id => $job_id, );
100         }
101         catch {
102             warn $_;
103             push @messages,
104               {
105                 type  => 'error',
106                 code  => 'cannot_enqueue_job',
107                 error => $_,
108               };
109             $template->param( view => 'errors' );
110         };
111     }
112
113     else {    # modification
114
115         my @item_columns = Koha::Items->columns;
116
117         my $new_item_data;
118         my ( $columns_with_regex );
119         my @subfields_to_blank = $input->multi_param('disable_input');
120         my @more_subfields = $input->multi_param("items.more_subfields_xml");
121         for my $item_column (@item_columns) {
122             my @attributes       = ($item_column);
123             my $cgi_param_prefix = 'items.';
124             if ( $item_column eq 'more_subfields_xml' ) {
125                 @attributes       = ();
126                 $cgi_param_prefix = 'items.more_subfields_xml_';
127                 for my $subfield (@more_subfields) {
128                     push @attributes, $subfield;
129                 }
130             }
131
132             for my $attr (@attributes) {
133
134                 my $cgi_var_name = $cgi_param_prefix
135                   . encode_utf8($attr)
136                   ;  # We need to deal correctly with encoding on subfield codes
137
138                 if ( grep { $cgi_var_name eq $_ } @subfields_to_blank ) {
139                     # Empty this column
140                     $new_item_data->{$attr} = undef;
141                 }
142                 elsif ( my $regex_search =
143                     $input->param( $cgi_var_name . '_regex_search' ) )
144                 {
145                     $columns_with_regex->{$attr} = {
146                         search => $regex_search,
147                         replace =>
148                           $input->param( $cgi_var_name . '_regex_replace' ),
149                         modifiers =>
150                           $input->param( $cgi_var_name . '_regex_modifiers' )
151                     };
152                 }
153                 else {
154                     my @v =
155                       grep { $_ ne "" } uniq $input->multi_param($cgi_var_name);
156
157                     next unless @v;
158
159                     $new_item_data->{$attr} = join '|', @v;
160                 }
161             }
162         }
163
164         my $params = {
165             record_ids                        => \@itemnumbers,
166             regex_mod                         => $columns_with_regex,
167             new_values                        => $new_item_data,
168             exclude_from_local_holds_priority => (
169                 defined $exclude_from_local_holds_priority
170                   && $exclude_from_local_holds_priority ne ""
171               )
172             ? $exclude_from_local_holds_priority
173             : undef,
174
175         };
176         try {
177             my $job_id =
178               Koha::BackgroundJob::BatchUpdateItem->new->enqueue($params);
179             $nextop = 'enqueued';
180             $template->param( job_id => $job_id, );
181         }
182         catch {
183             push @messages,
184               {
185                 type  => 'error',
186                 code  => 'cannot_enqueue_job',
187                 error => $_,
188               };
189             $template->param( view => 'errors' );
190         };
191     }
192
193 }
194
195 $template->param(
196     messages => \@messages,
197 );
198 #
199 #-------------------------------------------------------------------------------
200 # build screen with existing items. and "new" one
201 #-------------------------------------------------------------------------------
202
203 if ($op eq "show"){
204     my $filefh = $input->upload('uploadfile');
205     my $filecontent = $input->param('filecontent');
206     my ( @notfoundbarcodes, @notfounditemnumbers);
207
208     my $split_chars = C4::Context->preference('BarcodeSeparators');
209     if ($filefh){
210         binmode $filefh, ':encoding(UTF-8)';
211         my @contentlist;
212         while (my $content=<$filefh>){
213             $content =~ s/[\r\n]*$//;
214             push @contentlist, $content if $content;
215         }
216
217         if ($filecontent eq 'barcode_file') {
218             @contentlist = grep /\S/, ( map { split /[$split_chars]/ } @contentlist );
219             @contentlist = uniq @contentlist;
220             # Note: adding lc for case insensitivity
221             my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@contentlist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed };
222             @itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @contentlist;
223             @notfoundbarcodes = grep { !exists $itemdata{lc $_} } @contentlist;
224         }
225         elsif ( $filecontent eq 'itemid_file') {
226             @contentlist = uniq @contentlist;
227             my %itemdata = map { $_->{itemnumber} => 1 } @{ Koha::Items->search({ itemnumber => \@contentlist }, { columns => [ 'itemnumber' ] } )->unblessed };
228             @itemnumbers = grep { exists $itemdata{$_} } @contentlist;
229             @notfounditemnumbers = grep { !exists $itemdata{$_} } @contentlist;
230         }
231     } else {
232         if (defined $biblionumber && !@itemnumbers){
233             my $biblio = Koha::Biblios->find($biblionumber);
234             @itemnumbers = $biblio ? $biblio->items->get_column('itemnumber') : ();
235         }
236         if ( my $list = $input->param('barcodelist') ) {
237             my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
238             @barcodelist = uniq @barcodelist;
239
240             @barcodelist = map { barcodedecode( $_ ) } @barcodelist;
241
242             # Note: adding lc for case insensitivity
243             my %itemdata = map { lc($_->{barcode}) => $_->{itemnumber} } @{ Koha::Items->search({ barcode => \@barcodelist }, { columns => [ 'itemnumber', 'barcode' ] } )->unblessed };
244             @itemnumbers = map { exists $itemdata{lc $_} ? $itemdata{lc $_} : () } @barcodelist;
245             @notfoundbarcodes = grep { !exists $itemdata{lc $_} } @barcodelist;
246         }
247     }
248
249     # Flag to tell the template there are valid results, hidden or not
250     if(scalar(@itemnumbers) > 0){ $template->param("itemresults" => 1); }
251     # Only display the items if there are no more than pref MaxItemsToProcessForBatchMod or MaxItemsToDisplayForBatchDel
252     my $max_display_items = $del
253         ? C4::Context->preference("MaxItemsToDisplayForBatchDel")
254         : C4::Context->preference("MaxItemsToDisplayForBatchMod");
255     $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
256     if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
257         $display_items = 1;
258     } else {
259         $template->param("too_many_items_display" => scalar(@itemnumbers));
260         # Even if we do not display the items, we need the itemnumbers
261         $template->param(itemnumbers_array => \@itemnumbers);
262     }
263
264     # now, build the item form for entering a new item
265
266     # Getting list of subfields to keep when restricted batchmod edit is enabled
267     my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : ();
268
269     my $subfields = Koha::UI::Form::Builder::Item->new->edit_form(
270         {
271             restricted_editition => $restrictededition,
272             (
273                 @subfields_to_allow
274                 ? ( subfields_to_allow => \@subfields_to_allow )
275                 : ()
276             ),
277             ignore_not_allowed_subfields => 1,
278             kohafields_to_ignore         => ['items.barcode'],
279             prefill_with_default_values => $use_default_values,
280             default_branches_empty      => 1,
281         }
282     );
283
284     # what's the next op ? it's what we are not in : an add if we're editing, otherwise, and edit.
285     $template->param(
286         subfields           => $subfields,
287         notfoundbarcodes    => \@notfoundbarcodes,
288         notfounditemnumbers => \@notfounditemnumbers
289     );
290     $nextop="action"
291 } # -- End action="show"
292
293 if ( $display_items ) {
294     my $items_table =
295       Koha::UI::Table::Builder::Items->new( { itemnumbers => \@itemnumbers } )
296       ->build_table;
297     $template->param(
298         items        => $items_table->{items},
299         item_header_loop => $items_table->{headers},
300     );
301 }
302
303 $template->param(
304     op  => $nextop,
305     del => $del,
306     ( $op ? ( $op => 1 ) : () ),
307     src          => $src,
308     biblionumber => $biblionumber,
309 );
310
311 output_html_with_http_headers $input, $cookie, $template->output;