@@ -, +, @@ - Display a unique item block at once - PrepareItemrecordDisplay is now used for cloning block - New system preference UniqueItemFields - Values corresponding to fields in syspref are not duplicated when adding a new item (button 'Add') - When saving the form, a check is made on fields in syspref for detecting duplicate (in DB and in the form) --- acqui/check_uniqueness.pl | 68 ++++ acqui/neworderempty.pl | 16 +- acqui/orderreceive.pl | 15 +- installer/data/mysql/updatedatabase.pl | 11 + .../intranet-tmpl/prog/en/includes/additem.js.inc | 10 + koha-tmpl/intranet-tmpl/prog/en/js/additem.js | 337 ++++++++++++++------ .../prog/en/modules/acqui/neworderempty.tt | 157 +++++----- .../prog/en/modules/acqui/orderreceive.tt | 133 +++++--- .../en/modules/admin/preferences/acquisitions.pref | 3 + .../prog/en/modules/services/itemrecorddisplay.tt | 25 ++ services/itemrecorddisplay.pl | 58 ++++ 11 files changed, 590 insertions(+), 243 deletions(-) create mode 100755 acqui/check_uniqueness.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/additem.js.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt create mode 100755 services/itemrecorddisplay.pl --- a/acqui/check_uniqueness.pl +++ a/acqui/check_uniqueness.pl @@ -0,0 +1,68 @@ +#!/usr/bin/perl + +# Copyright 2011 BibLibre SARL +# +# 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 2 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. + +# This script search in items table if a value for a given field exists. +# It is used in check_additem (additem.js) +# Parameters are a list of 'field', which must be field names in items table +# and a list of 'value', which are the corresponding value to check +# Eg. @field = ('barcode', 'barcode', 'stocknumber') +# @value = ('1234', '1235', 'ABC') +# The script will check if there is already an item with barcode '1234', +# then an item with barcode '1235', and finally check if there is an item +# with stocknumber 'ABC' +# It returns a JSON string which contains what have been found +# Eg. { barcode: ['1234', '1235'], stocknumber: ['ABC'] } + +use Modern::Perl; + +use CGI; +use JSON; +use C4::Context; +use C4::Output; +use C4::Auth; + +my $input = new CGI; +my @field = $input->param('field'); +my @value = $input->param('value'); + +my $dbh = C4::Context->dbh; + +my $query = "SHOW COLUMNS FROM items"; +my $sth = $dbh->prepare($query); +$sth->execute; +my $results = $sth->fetchall_hashref('Field'); +my @columns = keys %$results; + +my $r = {}; +my $index = 0; +for my $f ( @field ) { + if(0 < grep /^$f$/, @columns) { + $query = "SELECT $f FROM items WHERE $f = ?"; + $sth = $dbh->prepare( $query ); + $sth->execute( $value[$index] ); + my @values = $sth->fetchrow_array; + + if ( @values ) { + push @{ $r->{$f} }, $values[0]; + } + } + $index++; +} + +output_with_http_headers $input, undef, to_json($r), 'json'; --- a/acqui/neworderempty.pl +++ a/acqui/neworderempty.pl @@ -317,17 +317,15 @@ if ($CGIsort2) { } if (C4::Context->preference('AcqCreateItem') eq 'ordering' && !$ordernumber) { - # prepare empty item form - my $cell = PrepareItemrecordDisplay('','','','ACQ'); -# warn "==> ".Data::Dumper::Dumper($cell); - unless ($cell) { - $cell = PrepareItemrecordDisplay('','','',''); + # Check if ACQ framework exists + my $marc = GetMarcStructure(1, 'ACQ'); + unless($marc) { $template->param('NoACQframework' => 1); } - my @itemloop; - push @itemloop,$cell; - - $template->param(items => \@itemloop); + $template->param( + AcqCreateItemOrdering => 1, + UniqueItemFields => C4::Context->preference('UniqueItemFields'), + ); } # Get the item types list, but only if item_level_itype is YES. Otherwise, it will be in the item, no need to display it in the biblio my @itemtypes; --- a/acqui/orderreceive.pl +++ a/acqui/orderreceive.pl @@ -116,16 +116,15 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( # prepare the form for receiving if ( $count == 1 ) { if (C4::Context->preference('AcqCreateItem') eq 'receiving') { - # prepare empty item form - my $cell = PrepareItemrecordDisplay('','','','ACQ'); - unless ($cell) { - $cell = PrepareItemrecordDisplay('','','',''); + # Check if ACQ framework exists + my $marc = GetMarcStructure(1, 'ACQ'); + unless($marc) { $template->param('NoACQframework' => 1); } - my @itemloop; - push @itemloop,$cell; - - $template->param(items => \@itemloop); + $template->param( + AcqCreateItemReceiving => 1, + UniqueItemFields => C4::Context->preference('UniqueItemFields'), + ); } if ( @$results[0]->{'quantityreceived'} == 0 ) { --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -4663,6 +4663,17 @@ ENDOFRENEWAL print "Upgrade to $DBversion done (Added a system preference to allow renewal of Patron account either from todays date or from existing expiry date in the patrons account.)\n"; SetVersion($DBversion); } + +$DBversion = "XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + $dbh->do(qq{ + INSERT INTO systempreferences(variable,value,explanation,options,type) + VALUES('UniqueItemFields', 'barcode', 'Space-separated list of fields that should be unique (used in acquisition module for item creation). Fields must be valid SQL column names of items table', '', 'Free') + }); + print "Upgrade to $DBversion done (Added system preference 'UniqueItemFields')\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 DropAllForeignKeys($table) --- a/koha-tmpl/intranet-tmpl/prog/en/includes/additem.js.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/additem.js.inc @@ -0,0 +1,10 @@ + --- a/koha-tmpl/intranet-tmpl/prog/en/js/additem.js +++ a/koha-tmpl/intranet-tmpl/prog/en/js/additem.js @@ -1,110 +1,239 @@ -function deleteItemBlock(index) { - var aDiv = document.getElementById(index); - aDiv.parentNode.removeChild(aDiv); - var quantity = document.getElementById('quantity'); - quantity.setAttribute('value',parseFloat(quantity.getAttribute('value'))-1); +function addItem( node, unique_item_fields ) { + var index = $(node).parent().attr('id'); + var current_qty = parseInt($("#quantity").val()); + var max_qty; + if($("#quantity_to_receive").length != 0){ + max_qty = parseInt($("#quantity_to_receive").val()); + } else { + max_qty = 99999; + } + if ( $("#items_list table").find('tr[idblock="' + index + '"]').length == 0 ) { + if ( current_qty < max_qty ) { + if ( current_qty < max_qty - 1 ) + cloneItemBlock(index, unique_item_fields); + addItemInList(index, unique_item_fields); + $("#" + index).find("a[name='buttonPlus']").text("Update"); + $("#quantity").val(current_qty + 1); + } else if ( current_qty >= max_qty ) { + alert(window.MSG_ADDITEM_JS_CANT_RECEIVE_MORE_ITEMS + || "You can't receive any more items."); + } + } else { + if ( current_qty < max_qty ) + cloneItemBlock(index, unique_item_fields); + var tr = constructTrNode(index); + $("#items_list table").find('tr[idblock="' + index + '"]:first').replaceWith(tr); + } + $("#" + index).hide(); +} + +function showItem(index) { + $("#outeritemblock").children("div").each(function(){ + if ( $(this).attr('id') == index ) { + $(this).show(); + } else { + if ( $("#items_list table").find('tr[idblock="' + $(this).attr('id') + '"]').length == 0 ) { + $(this).remove(); + } else { + $(this).hide(); + } + } + }); +} + +function constructTrNode(index, unique_item_fields) { + var fields = ['barcode', 'homebranch', 'holdingbranch', 'notforloan', + 'restricted', 'location', 'itemcallnumber', 'copynumber', + 'stocknumber', 'ccode', 'itype', 'materials', 'itemnotes']; + + var result = ""; + var edit_link = "" + + (window.MSG_ADDITEM_JS_EDIT || "Edit") + ""; + var del_link = "" + + (window.MSG_ADDITEM_JS_DELETE || "Delete") + ""; + result += "" + edit_link + ""; + result += "" + del_link + ""; + for(i in fields) { + var field = fields[i]; + var field_elt = $("#" + index) + .find("[name='kohafield'][value='items."+field+"']") + .prevAll("[name='field_value']")[0]; + var field_value; + if($(field_elt).is('select')) { + field_value = $(field_elt).find("option:selected").text(); + } else { + field_value = $(field_elt).val(); + } + result += "" + field_value + ""; + } + result += ""; + + return result; +} + +function addItemInList(index, unique_item_fields) { + $("#items_list").show(); + var tr = constructTrNode(index, unique_item_fields); + $("#items_list table tbody").append(tr); +} + +function deleteItemBlock(node_a, index, unique_item_fields) { + $("#" + index).remove(); + var current_qty = parseInt($("#quantity").val()); + var max_qty; + if($("#quantity_to_receive").length != 0) { + max_qty = parseInt($("#quantity_to_receive").val()); + } else { + max_qty = 99999; + } + $("#quantity").val(current_qty - 1); + $(node_a).parents('tr').remove(); + if(current_qty - 1 == 0) + $("#items_list").hide(); + + if ( $("#quantity").val() <= max_qty - 1) { + if ( $("#outeritemblock").children("div :visible").length == 0 ) { + $("#outeritemblock").children("div:last").show(); + } + } + if ( $("#quantity").val() == 0 && $("#outeritemblock > div").length == 0) { + cloneItemBlock(0, unique_item_fields); + } } -function cloneItemBlock(index) { - var original = document.getElementById(index); //original
- var clone = clone_with_selected(original) + +function cloneItemBlock(index, unique_item_fields) { + var original; + if(index) { + original = $("#" + index); //original
+ } + var dont_copy_fields = new Array(); + if(unique_item_fields) { + var dont_copy_fields = unique_item_fields.split(' '); + for(i in dont_copy_fields) { + dont_copy_fields[i] = "items." + dont_copy_fields[i]; + } + } + var random = Math.floor(Math.random()*100000); // get a random itemid. - // set the attribute for the new 'div' subfields - clone.setAttribute('id',index + random);//set another id. - var NumTabIndex; - NumTabIndex = parseInt(original.getAttribute('tabindex')); - if(isNaN(NumTabIndex)) NumTabIndex = 0; - clone.setAttribute('tabindex',NumTabIndex+1); - var CloneButtonPlus; - var CloneButtonMinus; - // try{ - var jclone = $(clone); - CloneButtonPlus = $("a.addItem", jclone).get(0); - CloneButtonPlus.setAttribute('onclick',"cloneItemBlock('" + index + random + "')"); - CloneButtonMinus = $("a.delItem", jclone).get(0); - CloneButtonMinus.setAttribute('onclick',"deleteItemBlock('" + index + random + "')"); - CloneButtonMinus.setAttribute('style',"display:inline"); - // change itemids of the clone - var elems = clone.getElementsByTagName('input'); - for( i = 0 ; elems[i] ; i++ ) - { - if(elems[i].name.match(/^itemid/)) { - elems[i].value = random; + var clone = $("
") + $.ajax({ + url: "/cgi-bin/koha/services/itemrecorddisplay.pl", + dataType: 'html', + data: { + frameworkcode: 'ACQ' + }, + success: function(data, textStatus, jqXHR) { + /* Create the item block */ + $(clone).append(data); + /* Change all itemid fields value */ + $(clone).find("input[name='itemid']").each(function(){ + $(this).val(random); + }); + /* Add buttons + and Clear */ + var buttonPlus = 'Add'; + var buttonClear = '' + (window.MSG_ADDITEM_JS_CLEAR || 'Clear') + ''; + $(clone).append(buttonPlus).append(buttonClear); + /* Copy values from the original block (input) */ + $(original).find("input[name='field_value']").each(function(){ + var kohafield = $(this).siblings("input[name='kohafield']").val(); + if($(this).val() && dont_copy_fields.indexOf(kohafield) == -1) { + $(this).parent("div").attr("id").match(/^(subfield.)/); + var id = RegExp.$1; + var value = $(this).val(); + $(clone).find("div[id^='"+id+"'] input[name='field_value']").val(value); + } + }); + /* Copy values from the original block (select) */ + $(original).find("select[name='field_value']").each(function(){ + var kohafield = $(this).siblings("input[name='kohafield']").val(); + if($(this).val() && dont_copy_fields.indexOf(kohafield) == -1) { + $(this).parent("div").attr("id").match(/^(subfield.)/); + var id = RegExp.$1; + var value = $(this).val(); + $(clone).find("div[id^='"+id+"'] select[name='field_value']").val(value); + } + }); + + $("#outeritemblock").append(clone); } - } - // } - //catch(e){ // do nothig if ButtonPlus & CloneButtonPlus don't exist. - //} - // insert this line on the page - original.parentNode.insertBefore(clone,original.nextSibling); - var quantity = document.getElementById('quantity'); - quantity.setAttribute('value',parseFloat(quantity.getAttribute('value'))+1); + }); } -function check_additem() { - var barcodes = document.getElementsByName('barcode'); - var success = true; - for(i=0;i j) && (barcodes[i].value == barcodes[j].value) && barcodes[i].value !='') { - barcodes[i].className='error'; - barcodes[j].className='error'; - success = false; - } - } - } - // TODO : Add AJAX function to test against barcodes already in the database, not just - // duplicates within the form. - return success; + +function clearItemBlock(node) { + var index = $(node).parent().attr('id'); + var block = $("#"+index); + $(block).find("input[type='text']").each(function(){ + $(this).val(""); + }); + $(block).find("select").each(function(){ + $(this).find("option:first").attr("selected", true); + }); +} + +function check_additem(unique_item_fields) { + var success = true; + var data = new Object(); + data['field'] = new Array(); + data['value'] = new Array(); + var array_fields = unique_item_fields.split(' '); + $(".error").empty(); // Clear error div + + // Check if a value is duplicated in form + for ( field in array_fields ) { + var fieldname = array_fields[field]; + var values = new Array(); + $("[name='kohafield'][value=items."+array_fields[field]+"]").each(function(){ + var input = $(this).prevAll("input[name='field_value']")[0]; + if($(input).val()) { + values.push($(input).val()); + data['field'].push(fieldname); + data['value'].push($(input).val()); + } + }); + + var sorted_arr = values.sort(); + for (var i = 0; i < sorted_arr.length - 1; i += 1) { + if (sorted_arr[i + 1] == sorted_arr[i]) { + $(".error").append( + fieldname + " '" + sorted_arr[i] + "' " + + (window.MSG_ADDITEM_JS_IS_DUPLICATE || "is duplicated") + + "
"); + success = false; + } + } + } + + // If there is a duplication, we raise an error + if ( success == false ) { + $(".error").show(); + return false; + } + + $.ajax({ + url: '/cgi-bin/koha/acqui/check_uniqueness.pl', + async: false, + dataType: 'json', + data: data, + success: function(data) { + for (field in data) { + success = false; + for (var i=0; i < data[field].length; i++) { + var value = data[field][i]; + $(".error").append( + field + " '" + value + "' " + + (window.MSG_ADDITEM_JS_ALREADY_EXISTS_IN_DB + || "already exists in database") + + "
" + ); + } + } + } + }); + + if ( success == false ) { + $(".error").show(); + } + return success; } -function clone_with_selected (node) { - var origin = node.getElementsByTagName("select"); - var tmp = node.cloneNode(true) - var selectelem = tmp.getElementsByTagName("select"); - for (var i=0; i +[% INCLUDE 'additem.js.inc' %] + [% INCLUDE 'header.inc' %] @@ -18,10 +55,11 @@

Receive items from : [% name %] [% IF ( invoice ) %][[% invoice %]] [% END %] (order #[% ordernumber %])

[% IF ( count ) %] -
+
- + +
Catalog Details
  1. Title: [% title |html %]
  2. @@ -35,48 +73,48 @@ [% seriestitle %]
- [% IF ( items ) %] -
- Item - [% IF ( NoACQframework ) %] -

No ACQ framework, using default. You should create a framework with code ACQ, the items framework would be used

- [% END %] + [% IF (AcqCreateItemReceiving) %] + - [% FOREACH item IN items %] -
-
-
    [% FOREACH iteminformatio IN item.iteminformation %]
  1. -
    - - - [% iteminformatio.marc_value %] - - - - - - [% IF ( iteminformatio.ITEM_SUBFIELDS_ARE_NOT_REPEATABLE ) %] - + - [% END %] - -
  2. +
    + Item + [% IF ( NoACQframework ) %] +

    + No ACQ framework, using default. You should create a + framework with code ACQ, the items framework would be + used +

    [% END %] -
- Add - -
-
- - - - - - - - - [% END %] -
- [% END %] +
+ + [% END %][%# IF (AcqCreateItemReceiving) %] @@ -94,12 +132,15 @@
  • [% IF ( memberfirstname and membersurname ) %][% IF ( memberfirstname ) %][% memberfirstname %][% END %] [% membersurname %][% ELSE %]No name[% END %]
  • [% IF ( edit ) %] - + [% ELSE %] - + [% END %]
  • + [% IF (AcqCreateItemReceiving) %] + + [% ELSE %] [% IF ( quantityreceived ) %] [% IF ( edit ) %] @@ -120,6 +161,7 @@ [% END %] [% END %] + [% END %][%# IF (AcqCreateItemReceiving) %]
  • @@ -135,7 +177,8 @@
    - Cancel + + Cancel
    [% ELSE %]
    --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref @@ -9,6 +9,9 @@ Acquisitions: receiving: receiving an order. cataloguing: cataloging the record. - + - pref: UniqueItemFields + - (space-separated list of fields that should be unique for items, must be valid SQL fields of items table) + - - When closing or reopening a basket, - pref: BasketConfirmations default: 1 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/services/itemrecorddisplay.tt @@ -0,0 +1,25 @@ +
      + [% FOREACH iteminfo IN iteminformation %] +
    1. +
      + + [% iteminfo.marc_value %] + + + + + +
      +
    2. + [% END %] +
    + --- a/services/itemrecorddisplay.pl +++ a/services/itemrecorddisplay.pl @@ -0,0 +1,58 @@ +#!/usr/bin/perl + +# Copyright 2011 BibLibre SARL +# 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 2 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 NAME + +itemrecorddisplay.pl + +=head1 DESCRIPTION + +Return a HTML form for Item record modification or creation. +It uses PrepareItemrecordDisplay + +=cut + +use strict; +use warnings; + +use CGI; +use C4::Auth; +use C4::Output; +use C4::Biblio; + +my $input = new CGI; +my ($template, $loggedinuser, $cookie, $flags) = get_template_and_user( { + template_name => 'services/itemrecorddisplay.tmpl', + query => $input, + type => 'intranet', + authnotrequired => 1, +} ); + +my $biblionumber = $input->param('biblionumber') || ''; +my $itemnumber = $input->param('itemnumber') || ''; +my $frameworkcode = $input->param('frameworkcode') || ''; + +my $result = PrepareItemrecordDisplay($biblionumber, $itemnumber, undef, $frameworkcode); +unless($result) { + $result = PrepareItemrecordDisplay($biblionumber, $itemnumber, undef, ''); +} + +$template->param(%$result); + +output_html_with_http_headers $input, $cookie, $template->output; + --