Bug 40391: Add support for GIR:LSL (Library Sub-Location) in EDIFACT processing
This implements support for the new EDItEUR/BiC GIR:LSL (Library Sub-Location) field to complement the existing GIR:LSQ (Library Sequence) functionality, allowing flexible mapping of both fields to location and collection item fields. Changes: - Add LSL mapping to GIR field extraction in Koha::Edifact::Line - Update gir_segments() in Koha::Edifact::Order to handle both LSL and LSQ preferences independently - Add validation functions in Koha::EDI using Koha::AuthorisedValues for location/collection codes - Add EdifactLSL system preference for independent LSL field mapping - Update EdifactLSQ to include empty option for ignore functionality - Add admin interface configuration for LSL preference The implementation provides maximum flexibility: - EdifactLSQ can be set to: location, ccode, or empty (ignore) - EdifactLSL can be set to: location, ccode, or empty (ignore) - Both fields can be mapped independently or to same target field - Validation ensures codes exist in LOC/CCODE authorised values - Maintains full backwards compatibility with existing LSQ-only setups Test plan: 1. Apply both patches 2. Update database: installer/data/mysql/updatedatabase.pl 3. Verify new system preferences in Administration > System preferences > Acquisitions: - EdifactLSQ: "Map EDI sequence code (GIR+LSQ) to Koha Item field" - EdifactLSL: "Map EDI sub-location code (GIR+LSL) to Koha Item field" 4. Create test authorised values: - LOC category: Add values like FICTION, REFERENCE - CCODE category: Add values like ADULT, CHILD 5. Set preferences to test combinations: - EdifactLSQ=location, EdifactLSL=ccode - EdifactLSQ=ccode, EdifactLSL=location - EdifactLSQ=location, EdifactLSL="" (empty) 6. Process EDIFACT messages containing GIR+LSL and GIR+LSQ segments 7. Verify items created with correct location/ccode values per preferences 8. Test invalid codes generate appropriate error messages 9. Confirm existing LSQ-only configurations continue working unchanged 10. Run: prove t/Edifact.t t/db_dependent/Koha/EDI.t t/db_dependent/Koha/Edifact/Order.t Sponsored-by: Westminster City Council <https://www.westminster.gov.uk/> Sponsored-by: Royal Borough of Kensington and Chelsea <https://www.rbkc.gov.uk/> Sponsored-by: OpenFifth <https://openfifth.co.uk/> Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
14064e9459
commit
76c2523b12
113
Koha/EDI.pm
113
Koha/EDI.pm
|
|
@ -54,6 +54,7 @@ use Koha::Plugins; # Adds plugin dirs to @INC
|
|||
use Koha::Plugins::Handler;
|
||||
use Koha::Acquisition::Baskets;
|
||||
use Koha::Acquisition::Booksellers;
|
||||
use Koha::AuthorisedValues;
|
||||
use Koha::Util::FrameworkPlugin qw( biblio_008 );
|
||||
|
||||
our $VERSION = 1.1;
|
||||
|
|
@ -1083,7 +1084,29 @@ sub quote_item {
|
|||
};
|
||||
|
||||
my $lsq_field = C4::Context->preference('EdifactLSQ');
|
||||
$new_item->{$lsq_field} = $item->girfield( 'sequence_code', $occurrence );
|
||||
my $lsl_field = C4::Context->preference('EdifactLSL');
|
||||
|
||||
# Handle LSQ field
|
||||
if ( $lsq_field && $item->girfield( 'sequence_code', $occurrence ) ) {
|
||||
my $lsq_value = $item->girfield( 'sequence_code', $occurrence );
|
||||
my $valid =
|
||||
( $lsq_field eq 'location' )
|
||||
? _validate_location_code( $lsq_value, $quote_message )
|
||||
: _validate_collection_code( $lsq_value, $quote_message );
|
||||
|
||||
$new_item->{$lsq_field} = $lsq_value if $valid;
|
||||
}
|
||||
|
||||
# Handle LSL field
|
||||
if ( $lsl_field && $item->girfield( 'sub_location_code', $occurrence ) ) {
|
||||
my $lsl_value = $item->girfield( 'sub_location_code', $occurrence );
|
||||
my $valid =
|
||||
( $lsl_field eq 'location' )
|
||||
? _validate_location_code( $lsl_value, $quote_message )
|
||||
: _validate_collection_code( $lsl_value, $quote_message );
|
||||
|
||||
$new_item->{$lsl_field} = $lsl_value if $valid;
|
||||
}
|
||||
|
||||
if ( $new_item->{itype} ) {
|
||||
$item_hash->{itype} = $new_item->{itype};
|
||||
|
|
@ -1091,6 +1114,9 @@ sub quote_item {
|
|||
if ( $new_item->{$lsq_field} ) {
|
||||
$item_hash->{$lsq_field} = $new_item->{$lsq_field};
|
||||
}
|
||||
if ( $new_item->{$lsl_field} ) {
|
||||
$item_hash->{$lsl_field} = $new_item->{$lsl_field};
|
||||
}
|
||||
if ( $new_item->{itemcallnumber} ) {
|
||||
$item_hash->{itemcallnumber} = $new_item->{itemcallnumber};
|
||||
}
|
||||
|
|
@ -1167,7 +1193,29 @@ sub quote_item {
|
|||
homebranch => $item->girfield( 'branch', $occurrence ),
|
||||
};
|
||||
my $lsq_field = C4::Context->preference('EdifactLSQ');
|
||||
$new_item->{$lsq_field} = $item->girfield( 'sequence_code', $occurrence );
|
||||
my $lsl_field = C4::Context->preference('EdifactLSL');
|
||||
|
||||
# Handle LSQ mapping with validation
|
||||
if ( $lsq_field && $item->girfield( 'sequence_code', $occurrence ) ) {
|
||||
my $lsq_value = $item->girfield( 'sequence_code', $occurrence );
|
||||
my $valid =
|
||||
( $lsq_field eq 'location' )
|
||||
? _validate_location_code( $lsq_value, $quote_message )
|
||||
: _validate_collection_code( $lsq_value, $quote_message );
|
||||
|
||||
$new_item->{$lsq_field} = $lsq_value if $valid;
|
||||
}
|
||||
|
||||
# Handle LSL mapping with validation
|
||||
if ( $lsl_field && $item->girfield( 'sub_location_code', $occurrence ) ) {
|
||||
my $lsl_value = $item->girfield( 'sub_location_code', $occurrence );
|
||||
my $valid =
|
||||
( $lsl_field eq 'location' )
|
||||
? _validate_location_code( $lsl_value, $quote_message )
|
||||
: _validate_collection_code( $lsl_value, $quote_message );
|
||||
|
||||
$new_item->{$lsl_field} = $lsl_value if $valid;
|
||||
}
|
||||
$new_item->{biblionumber} = $bib->{biblionumber};
|
||||
$new_item->{biblioitemnumber} = $bib->{biblioitemnumber};
|
||||
my $kitem = Koha::Item->new($new_item)->store;
|
||||
|
|
@ -1217,6 +1265,54 @@ sub get_edifact_ean {
|
|||
return $eans->[0];
|
||||
}
|
||||
|
||||
sub _validate_location_code {
|
||||
my ( $location_code, $quote_message ) = @_;
|
||||
|
||||
return 1 unless $location_code; # Skip if empty
|
||||
|
||||
my $av = Koha::AuthorisedValues->find(
|
||||
{
|
||||
category => 'LOC',
|
||||
authorised_value => $location_code
|
||||
}
|
||||
);
|
||||
|
||||
unless ($av) {
|
||||
$quote_message->add_to_edifact_errors(
|
||||
{
|
||||
section => "GIR+LSQ/LSL validation",
|
||||
details => "Invalid location code '$location_code' - not found in LOC authorized values"
|
||||
}
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _validate_collection_code {
|
||||
my ( $ccode, $quote_message ) = @_;
|
||||
|
||||
return 1 unless $ccode; # Skip if empty
|
||||
|
||||
my $av = Koha::AuthorisedValues->find(
|
||||
{
|
||||
category => 'CCODE',
|
||||
authorised_value => $ccode
|
||||
}
|
||||
);
|
||||
|
||||
unless ($av) {
|
||||
$quote_message->add_to_edifact_errors(
|
||||
{
|
||||
section => "GIR+LSQ/LSL validation",
|
||||
details => "Invalid collection code '$ccode' - not found in CCODE authorized values"
|
||||
}
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# We should not need to have a routine to do this here
|
||||
sub _discounted_price {
|
||||
my ( $discount, $price, $discounted_price ) = @_;
|
||||
|
|
@ -1370,8 +1466,19 @@ sub _create_item_from_quote {
|
|||
$item_hash->{booksellerid} = $quote->vendor_id;
|
||||
$item_hash->{price} = $item_hash->{replacementprice} = $item->price;
|
||||
$item_hash->{itype} = $item->girfield('stock_category');
|
||||
|
||||
my $lsq_field = C4::Context->preference('EdifactLSQ');
|
||||
$item_hash->{$lsq_field} = $item->girfield('sequence_code');
|
||||
my $lsl_field = C4::Context->preference('EdifactLSL');
|
||||
|
||||
# Handle LSQ mapping
|
||||
if ( $lsq_field && $item->girfield('sequence_code') ) {
|
||||
$item_hash->{$lsq_field} = $item->girfield('sequence_code');
|
||||
}
|
||||
|
||||
# Handle LSL mapping
|
||||
if ( $lsl_field && $item->girfield('sub_location_code') ) {
|
||||
$item_hash->{$lsl_field} = $item->girfield('sub_location_code');
|
||||
}
|
||||
|
||||
my $note = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -658,6 +658,7 @@ sub extract_gir {
|
|||
LRS => 'record_sublocation',
|
||||
LSM => 'shelfmark',
|
||||
LSQ => 'sequence_code',
|
||||
LSL => 'sub_location_code',
|
||||
LST => 'stock_category',
|
||||
LSZ => 'size_code',
|
||||
LVC => 'coded_servicing_instruction',
|
||||
|
|
|
|||
|
|
@ -388,6 +388,7 @@ sub order_line {
|
|||
# we dont currently support this in koha
|
||||
# GIR copy-related data
|
||||
my $lsq_field = C4::Context->preference('EdifactLSQ');
|
||||
my $lsl_field = C4::Context->preference('EdifactLSL');
|
||||
my @items;
|
||||
if ( $basket->effective_create_items eq 'ordering' ) {
|
||||
|
||||
|
|
@ -395,12 +396,23 @@ sub order_line {
|
|||
foreach my $item (@linked_itemnumbers) {
|
||||
my $i_obj = $schema->resultset('Item')->find( $item->itemnumber );
|
||||
if ( defined $i_obj ) {
|
||||
push @items, {
|
||||
my $item_data = {
|
||||
branchcode => $i_obj->get_column('homebranch'),
|
||||
itype => $i_obj->effective_itemtype,
|
||||
$lsq_field => $i_obj->$lsq_field,
|
||||
itemcallnumber => $i_obj->itemcallnumber,
|
||||
};
|
||||
|
||||
# Handle LSQ mapping
|
||||
if ($lsq_field) {
|
||||
$item_data->{$lsq_field} = $i_obj->$lsq_field;
|
||||
}
|
||||
|
||||
# Handle LSL mapping
|
||||
if ($lsl_field) {
|
||||
$item_data->{$lsl_field} = $i_obj->$lsl_field;
|
||||
}
|
||||
|
||||
push @items, $item_data;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -421,13 +433,23 @@ sub order_line {
|
|||
|
||||
my $item_fields = [];
|
||||
for my $item (@items) {
|
||||
push @{$item_fields},
|
||||
{
|
||||
my $item_field_data = {
|
||||
branchcode => $item->{branchcode},
|
||||
itype => $item->{itype},
|
||||
$lsq_field => $item->{$lsq_field},
|
||||
itemcallnumber => $item->{itemcallnumber},
|
||||
};
|
||||
};
|
||||
|
||||
# Handle LSQ mapping
|
||||
if ( $lsq_field && $item->{$lsq_field} ) {
|
||||
$item_field_data->{$lsq_field} = $item->{$lsq_field};
|
||||
}
|
||||
|
||||
# Handle LSL mapping
|
||||
if ( $lsl_field && $item->{$lsl_field} ) {
|
||||
$item_field_data->{$lsl_field} = $item->{$lsl_field};
|
||||
}
|
||||
|
||||
push @{$item_fields}, $item_field_data;
|
||||
}
|
||||
$self->add_seg(
|
||||
gir_segments(
|
||||
|
|
@ -558,6 +580,7 @@ sub gir_segments {
|
|||
my @segments;
|
||||
my $sequence_no = 1;
|
||||
my $lsq_field = C4::Context->preference('EdifactLSQ');
|
||||
my $lsl_field = C4::Context->preference('EdifactLSL');
|
||||
foreach my $item (@onorderitems) {
|
||||
my $elements_added = 0;
|
||||
my @gir_elements;
|
||||
|
|
@ -573,10 +596,18 @@ sub gir_segments {
|
|||
push @gir_elements,
|
||||
{ identity_number => 'LST', data => $item->{itype} };
|
||||
}
|
||||
if ( $item->{$lsq_field} ) {
|
||||
|
||||
# Handle LSQ mapping
|
||||
if ( $lsq_field && $item->{$lsq_field} ) {
|
||||
push @gir_elements,
|
||||
{ identity_number => 'LSQ', data => $item->{$lsq_field} };
|
||||
}
|
||||
|
||||
# Handle LSL mapping
|
||||
if ( $lsl_field && $item->{$lsl_field} ) {
|
||||
push @gir_elements,
|
||||
{ identity_number => 'LSL', data => $item->{$lsl_field} };
|
||||
}
|
||||
if ( $item->{itemcallnumber} ) {
|
||||
push @gir_elements,
|
||||
{ identity_number => 'LSM', data => $item->{itemcallnumber} };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
use Modern::Perl;
|
||||
use Koha::Installer::Output qw(say_warning say_success say_info);
|
||||
|
||||
return {
|
||||
bug_number => "40391",
|
||||
description => "Add EdifactLSL system preference for GIR:LSL (Library Sub-Location) field mapping",
|
||||
up => sub {
|
||||
my ($args) = @_;
|
||||
my ( $dbh, $out ) = @$args{qw(dbh out)};
|
||||
|
||||
# Update existing EdifactLSQ preference to include empty option
|
||||
$dbh->do(
|
||||
q{
|
||||
UPDATE systempreferences
|
||||
SET options = 'location|ccode|', explanation = 'Map EDI sequence code (GIR+LSQ) to Koha Item field, empty to ignore'
|
||||
WHERE variable = 'EdifactLSQ'
|
||||
}
|
||||
);
|
||||
|
||||
# Add new EdifactLSL preference
|
||||
$dbh->do(
|
||||
q{
|
||||
INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
|
||||
VALUES ('EdifactLSL', '', 'location|ccode|', 'Map EDI sub-location code (GIR+LSL) to Koha Item field, empty to ignore', 'Choice')
|
||||
}
|
||||
);
|
||||
|
||||
say_success( $out, "Updated EdifactLSQ system preference to include empty option" );
|
||||
say_success( $out, "Added EdifactLSL system preference for GIR:LSL field mapping" );
|
||||
},
|
||||
};
|
||||
|
|
@ -236,7 +236,8 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
|
|||
('EasyAnalyticalRecords','0','','If on, display in the catalogue screens tools to easily setup analytical record relationships','YesNo'),
|
||||
('EDIFACT','0',NULL,'Enables EDIFACT acquisitions functions','YesNo'),
|
||||
('EdifactInvoiceImport', 'automatic', 'automatic|manual', "If on, don't auto-import EDI invoices, just keep them in the database with the status 'new'", 'Choice'),
|
||||
('EdifactLSQ', 'location', 'location|ccode', 'Map EDI sequence code (GIR+LSQ) to Koha Item field', 'Choice'),
|
||||
('EdifactLSL', 'ccode', 'location|ccode|', 'Map EDI sub-location code (GIR+LSL) to Koha Item field, empty to ignore', 'Choice'),
|
||||
('EdifactLSQ', 'location', 'location|ccode|', 'Map EDI sequence code (GIR+LSQ) to Koha Item field, empty to ignore', 'Choice'),
|
||||
('ElasticsearchBoostFieldMatch', '0', NULL, 'Add a "match" query to es when searching, will follow indexes chosen in advanced search, or use title-cover for generic keyword or title index search', 'YesNo'),
|
||||
('ElasticsearchCrossFields', '1', '', 'Enable "cross_fields" option for searches using Elastic search.', 'YesNo'),
|
||||
('ElasticsearchIndexStatus_authorities', '0', 'Authorities index status', NULL, NULL),
|
||||
|
|
|
|||
|
|
@ -185,4 +185,14 @@ Acquisitions:
|
|||
choices:
|
||||
location: "location"
|
||||
ccode: "collection"
|
||||
"": "ignore"
|
||||
- " in items."
|
||||
-
|
||||
- "Map sub-location code (GIR:LSL) field to "
|
||||
- pref: EdifactLSL
|
||||
default: ""
|
||||
choices:
|
||||
location: "location"
|
||||
ccode: "collection"
|
||||
"": "ignore"
|
||||
- " in items."
|
||||
|
|
|
|||
Loading…
Reference in New Issue