1 package Koha
::Edifact
::Line
;
3 # Copyright 2014, 2015 PTFS-Europe Ltd
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
29 my ( $class, $data_array_ref ) = @_;
30 my $self = _parse_lines
($data_array_ref);
36 # helper routine used by constructor
37 # creates the hashref used as a data structure by the Line object
42 my $lin = shift @
{$aref};
44 my $id = $lin->elem( 2, 0 ); # may be undef in ordrsp
45 my $action = $lin->elem( 1, 0 );
47 line_item_number
=> $lin->elem(0),
48 action_notification
=> $action,
49 item_number_id
=> $id,
50 additional_product_ids
=> [],
54 foreach my $s ( @
{$aref} ) {
55 if ( $s->tag eq 'PIA' ) {
56 push @
{ $d->{additional_product_ids
} },
58 function_code
=> $s->elem(0),
59 item_number
=> $s->elem( 1, 0 ),
60 number_type
=> $s->elem( 1, 1 ),
63 elsif ( $s->tag eq 'IMD' ) {
64 push @item_description, $s;
66 elsif ( $s->tag eq 'QTY' ) {
67 $d->{quantity
} = $s->elem( 0, 1 );
69 elsif ( $s->tag eq 'DTM' ) {
70 if ( $s->elem( 0, 0 ) eq '44' ) {
71 $d->{availability_date
} = $s->elem( 0, 1 );
74 elsif ( $s->tag eq 'GIR' ) {
76 # we may get a Gir for each copy if QTY > 1
79 push @
{ $d->{GIR
} }, extract_gir
($s);
82 my $gir = extract_gir
($s);
83 if ( $gir->{copy
} ) { # may have to merge
84 foreach my $g ( @
{ $d->{GIR
} } ) {
85 if ( $gir->{copy
} eq $g->{copy
} ) {
86 foreach my $field ( keys %{$gir} ) {
87 if ( !exists $g->{$field} ) {
88 $g->{$field} = $gir->{$field};
96 push @
{ $d->{GIR
} }, $gir;
101 elsif ( $s->tag eq 'FTX' ) {
103 my $type = $s->elem(0);
104 my $ctype = 'coded_free_text';
105 if ( $type eq 'LNO' ) { # Ingrams Oasis Internal Notes field
106 $type = 'internal_notes';
107 $ctype = 'coded_internal_note';
109 elsif ( $type eq 'LIN' ) {
110 $type = 'orderline_free_text';
111 $ctype = 'coded_orderline_text';
113 elsif ( $type eq 'SUB' ) {
114 $type = 'coded_substitute_text';
120 my $coded_text = $s->elem(2);
121 if ( ref $coded_text eq 'ARRAY' && $coded_text->[0] ) {
122 $d->{$ctype}->{table
} = $coded_text->[1];
123 $d->{$ctype}->{code
} = $coded_text->[0];
126 my $ftx = $s->elem(3);
127 if ( ref $ftx eq 'ARRAY' ) { # it comes in 70 character components
128 $ftx = join q{ }, @
{$ftx};
130 if ( exists $d->{$type} ) { # we can only catenate repeats
138 elsif ( $s->tag eq 'MOA' ) {
140 $d->{monetary_amount
} = $s->elem( 0, 1 );
142 elsif ( $s->tag eq 'PRI' ) {
144 $d->{price
} = $s->elem( 0, 1 );
146 elsif ( $s->tag eq 'RFF' ) {
147 my $qualifier = $s->elem( 0, 0 );
148 if ( $qualifier eq 'QLI' ) { # Suppliers unique quotation reference
149 $d->{reference
} = $s->elem( 0, 1 );
151 elsif ( $qualifier eq 'LI' ) { # Buyer's unique orderline number
152 $d->{ordernumber
} = $s->elem( 0, 1 );
154 elsif ( $qualifier eq 'SLI' )
155 { # Suppliers unique order line reference number
156 $d->{orderline_reference_number
} = $s->elem( 0, 1 );
160 $d->{item_description
} = _format_item_description
(@item_description);
166 sub _format_item_description
{
170 # IMD : +Type code 'L' + characteristic code 3 char + Description in comp 3 & 4
171 foreach my $imd (@imd) {
172 my $type_code = $imd->elem(0);
173 my $ccode = $imd->elem(1);
174 my $desc = $imd->elem( 2, 3 );
175 if ( $imd->elem( 2, 4 ) ) {
176 $desc .= $imd->elem( 2, 4 );
178 if ( $type_code ne 'L' ) {
180 "Only handles text item descriptions at present: code=$type_code";
183 if ( exists $bibrec->{$ccode} ) {
184 $bibrec->{$ccode} .= q{ };
185 $bibrec->{$ccode} .= $desc;
188 $bibrec->{$ccode} = $desc;
196 my $b = $self->{item_description
};
198 my $bib = MARC
::Record
->new();
202 if ( exists $b->{'010'} ) {
203 @spec = qw( 100 a 011 c 012 b 013 d 014 e );
204 push @fields, new_field
( $b, [ 100, 1, q{ } ], @spec );
206 if ( exists $b->{'020'} ) {
207 @spec = qw( 020 a 021 c 022 b 023 d 024 e );
208 push @fields, new_field
( $b, [ 700, 1, q{ } ], @spec );
212 if ( exists $b->{'030'} ) {
213 push @fields, $self->corpcon(1);
215 if ( exists $b->{'040'} ) {
216 push @fields, $self->corpcon(7);
218 if ( exists $b->{'050'} ) {
219 @spec = qw( '050' a '060' b '065' c );
220 push @fields, new_field
( $b, [ 245, 1, 0 ], @spec );
222 if ( exists $b->{100} ) {
223 @spec = qw( 100 a 101 b);
224 push @fields, new_field
( $b, [ 250, q{ }, q{ } ], @spec );
226 @spec = qw( 110 a 120 b 170 c );
227 my $f = new_field
( $b, [ 260, q{ }, q{ } ], @spec );
231 @spec = qw( 180 a 181 b 182 c 183 e);
232 $f = new_field
( $b, [ 300, q{ }, q{ } ], @spec );
236 if ( exists $b->{190} ) {
238 push @fields, new_field
( $b, [ 490, q{ }, q{ } ], @spec );
241 if ( exists $b->{200} ) {
243 push @fields, new_field
( $b, [ 490, q{ }, q{ } ], @spec );
245 if ( exists $b->{210} ) {
247 push @fields, new_field
( $b, [ 490, q{ }, q{ } ], @spec );
249 if ( exists $b->{300} ) {
251 push @fields, new_field
( $b, [ 500, q{ }, q{ } ], @spec );
253 if ( exists $b->{310} ) {
255 push @fields, new_field
( $b, [ 520, q{ }, q{ } ], @spec );
257 if ( exists $b->{320} ) {
259 push @fields, new_field
( $b, [ 521, q{ }, q{ } ], @spec );
261 if ( exists $b->{260} ) {
263 push @fields, new_field
( $b, [ 600, q{ }, q{ } ], @spec );
265 if ( exists $b->{270} ) {
267 push @fields, new_field
( $b, [ 650, q{ }, q{ } ], @spec );
269 if ( exists $b->{280} ) {
271 push @fields, new_field
( $b, [ 655, q{ }, q{ } ], @spec );
275 if ( exists $b->{230} ) {
277 push @fields, new_field
( $b, [ '082', q{ }, q{ } ], @spec );
279 if ( exists $b->{240} ) {
281 push @fields, new_field
( $b, [ '084', q{ }, q{ } ], @spec );
283 $bib->insert_fields_ordered(@fields);
289 my ( $self, $level ) = @_;
291 1 => [ '033', '032', '034' ],
292 7 => [ '043', '042', '044' ],
295 foreach my $t ( @
{ $test_these->{$level} } ) {
296 if ( exists $self->{item_description
}->{$t} ) {
302 my ( $i1, $i2 ) = ( q{ }, q{ } );
304 $tag = ( $level * 100 ) + 11;
306 @spec = qw( 030 a 031 e 032 n 033 c 034 d);
309 @spec = qw( 040 a 041 e 042 n 043 c 044 d);
313 $tag = ( $level * 100 ) + 10;
315 @spec = qw( 030 a 031 b);
318 @spec = qw( 040 a 041 b);
321 return new_field
( $self->{item_description
}, [ $tag, $i1, $i2 ], @spec );
325 my ( $b, $tag_ind, @sfd_elem ) = @_;
328 my $e = shift @sfd_elem;
329 my $c = shift @sfd_elem;
330 if ( exists $b->{$e} ) {
331 push @sfd, $c, $b->{$e};
335 my $field = MARC
::Field
->new( @
{$tag_ind}, @sfd );
341 # Accessor methods to line data
345 return $self->{item_number_id
};
348 sub line_item_number
{
350 return $self->{line_item_number
};
353 sub additional_product_ids
{
355 return $self->{additional_product_ids
};
358 sub action_notification
{
360 my $a = $self->{action_notification
};
362 $a = _translate_action
($a); # return the associated text string
367 sub item_description
{
369 return $self->{item_description
};
372 sub monetary_amount
{
374 return $self->{monetary_amount
};
379 return $self->{quantity
};
384 return $self->{price
};
389 return $self->{reference
};
392 sub orderline_reference_number
{
394 return $self->{orderline_reference_number
};
399 return $self->{ordernumber
};
404 return $self->{free_text
};
407 sub coded_free_text
{
409 return $self->{coded_free_text
}->{code
};
414 return $self->{internal_notes
};
417 sub coded_internal_note
{
419 return $self->{coded_internal_note
}->{code
};
422 sub orderline_free_text
{
424 return $self->{orderline_free_text
};
427 sub coded_orderline_text
{
429 my $code = $self->{coded_orderline_text
}->{code
};
430 my $table = $self->{coded_orderline_text
}->{table
};
432 if ( $table eq '8B' || $table eq '7B' ) {
433 $txt = translate_8B
($code);
435 elsif ( $table eq '12B' ) {
436 $txt = translate_12B
($code);
438 if ( !$txt || $txt eq 'no match' ) {
444 sub substitute_free_text
{
446 return $self->{substitute_free_text
};
449 sub coded_substitute_text
{
451 return $self->{coded_substitute_text
}->{code
};
454 # This will take a standard code as returned
455 # by (orderline|substitue)-free_text (FTX seg LIN)
456 # and expand it useing EditEUR code list 8B
460 # list 7B is a subset of this
462 AB
=> 'Publication abandoned',
463 AD
=> 'Apply direct',
464 AU
=> 'Publisher address unknown',
465 CS
=> 'Status uncertain',
466 FQ
=> 'Only available abroad',
467 HK
=> 'Paperback OP: Hardback available',
469 IP
=> 'In print and in stock at publisher',
470 MD
=> 'Manufactured on demand',
471 NK
=> 'Item not known',
472 NN
=> 'We do not supply this item',
473 NP
=> 'Not yet published',
475 NS
=> 'Not sold separately',
476 OB
=> 'Temporarily out of stock',
477 OF
=> 'This format out of print: other format available',
478 OP
=> 'Out of print',
479 OR
=> 'Out pf print; New Edition coming',
480 PK
=> 'Hardback out of print: paperback available',
481 PN
=> 'Publisher no longer in business',
482 RE
=> 'Awaiting reissue',
483 RF
=> 'refer to other publisher or distributor',
486 RR
=> 'Rights restricted: cannot supply in this market',
488 SN
=> 'Our supplier cannot trace',
489 SO
=> 'Pack or set not available: single items only',
490 ST
=> 'Stocktaking: temporarily unavailable',
491 TO
=> 'Only to order',
492 TU
=> 'Temporarily unavailable',
493 UB
=> 'Item unobtainable from our suppliers',
494 UC
=> 'Unavailable@ reprint under consideration',
497 if ( exists $code_list_8B{$code} ) {
498 return $code_list_8B{$code};
508 my %code_list_12B = (
509 100 => 'Order line accepted',
510 101 => 'Price query: orderline will be held awaiting customer response',
512 'Discount query: order line will be held awaiting customer response',
513 103 => 'Minimum order value not reached: order line will be held',
515 'Firm order required: order line will be held awaiting customer response',
516 110 => 'Order line accepted, substitute product will be supplied',
517 200 => 'Order line not accepted',
518 201 => 'Price query: order line not accepted',
519 202 => 'Discount query: order line not accepted',
520 203 => 'Minimum order value not reached: order line not accepted',
521 205 => 'Order line not accepted: quoted promotion is invalid',
522 206 => 'Order line not accepted: quoted promotion has ended',
524 'Order line not accepted: customer ineligible for quoted promotion',
525 210 => 'Order line not accepted: substitute product is offered',
526 220 => 'Oustanding order line cancelled: reason unspecified',
527 221 => 'Oustanding order line cancelled: past order expiry date',
528 222 => 'Oustanding order line cancelled by customer request',
529 223 => 'Oustanding order line cancelled: unable to supply',
530 300 => 'Order line passed to new supplier',
531 301 => 'Order line passed to secondhand department',
532 400 => 'Backordered - awaiting supply',
533 401 => 'On order from our supplier',
534 402 => 'On order from abroad',
535 403 => 'Backordered, waiting to reach minimum order value',
536 404 => 'Despatched from our supplier, awaiting delivery',
537 405 => 'Our supplier sent wrong item(s), re-ordered',
538 406 => 'Our supplier sent short, re-ordered',
539 407 => 'Our supplier sent damaged item(s), re-ordered',
540 408 => 'Our supplier sent imperfect item(s), re-ordered',
541 409 => 'Our supplier cannot trace order, re-ordered',
542 410 => 'Ordered item(s) being processed by bookseller',
544 'Ordered item(s) being processed by bookseller, awaiting customer action',
545 412 => 'Order line held awaiting customer instruction',
546 500 => 'Order line on hold - contact customer service',
547 800 => 'Order line already despatched',
548 900 => 'Cannot trace order line',
549 901 => 'Order line held: note title change',
550 902 => 'Order line held: note availability date delay',
551 903 => 'Order line held: note price change',
552 999 => 'Temporary hold: order action not yet determined',
555 if ( exists $code_list_12B{$code} ) {
556 return $code_list_12B{$code};
563 # item_desription_fields accessors
567 my $titlefield = q{050};
568 if ( exists $self->{item_description
}->{$titlefield} ) {
569 return $self->{item_description
}->{$titlefield};
577 if ( exists $self->{item_description
}->{$field} ) {
578 my $a = $self->{item_description
}->{$field};
579 my $forename_field = q{011};
580 if ( exists $self->{item_description
}->{$forename_field} ) {
582 $a .= $self->{item_description
}->{$forename_field};
592 if ( exists $self->{item_description
}->{$field} ) {
593 return $self->{item_description
}->{$field};
601 if ( exists $self->{item_description
}->{$field} ) {
602 return $self->{item_description
}->{$field};
607 sub publication_date
{
610 if ( exists $self->{item_description
}->{$field} ) {
611 return $self->{item_description
}->{$field};
619 if ( exists $self->{item_description
}->{$field} ) {
620 return $self->{item_description
}->{$field};
628 if ( exists $self->{item_description
}->{$field} ) {
629 return $self->{item_description
}->{$field};
635 my ( $self, $field, $occ ) = @_;
636 if ( $self->number_of_girs ) {
638 # defaults to occurence 0 returns undef if occ requested > occs
639 if ( defined $occ && $occ >= @
{ $self->{GIR
} } ) {
643 return $self->{GIR
}->[$occ]->{$field};
652 if ( $self->{GIR
} ) {
654 my $qty = @
{ $self->{GIR
} };
667 LAF
=> 'first_accession_number',
668 LAL
=> 'last_accession_number',
669 LCL
=> 'classification',
670 LCO
=> 'item_unique_id',
672 LFH
=> 'feature_heading',
673 LFN
=> 'fund_allocation',
674 LFS
=> 'filing_suffix',
675 LLN
=> 'loan_category',
677 LLS
=> 'label_sublocation',
678 LQT
=> 'part_order_quantity',
679 LRS
=> 'record_sublocation',
681 LSQ
=> 'collection_code',
682 LST
=> 'stock_category',
684 LVC
=> 'coded_servicing_instruction',
685 LVT
=> 'servicing_instruction',
688 my $set_qualifier = $s->elem( 0, 0 ); # copy number
689 my $gir_element = { copy
=> $set_qualifier, };
691 while ( my $e = $s->elem($element) ) {
693 if ( exists $qmap{ $e->[1] } ) {
694 my $qualifier = $qmap{ $e->[1] };
695 $gir_element->{$qualifier} = $e->[0];
699 carp
"Unrecognized GIR code : $e->[1] for $e->[0]";
705 # mainly for invoice processing amt_ will derive from MOA price_ from PRI and tax_ from TAX/MOA pairsn
707 my ( $self, $qualifier ) = @_;
708 foreach my $s ( @
{ $self->{segs
} } ) {
709 if ( $s->tag eq 'MOA' && $s->elem( 0, 0 ) eq $qualifier ) {
710 return $s->elem( 0, 1 );
718 return $self->moa_amt('52');
723 return $self->moa_amt('113');
726 # total including allowances & tax
729 return $self->moa_amt('128');
732 # Used to give price in currency other than that given in price
735 return $self->moa_amt('146');
738 # item amount after allowances excluding tax
741 return $self->moa_amt('203');
745 my ( $self, $price_qualifier ) = @_;
746 foreach my $s ( @
{ $self->{segs
} } ) {
747 if ( $s->tag eq 'PRI' && $s->elem( 0, 0 ) eq $price_qualifier ) {
749 price
=> $s->elem( 0, 1 ),
750 type
=> $s->elem( 0, 2 ),
751 type_qualifier
=> $s->elem( 0, 3 ),
758 # unit price that will be chaged excl tax
761 my $p = $self->pri_price('AAA');
768 # unit price excluding all allowances, charges and taxes
771 my $p = $self->pri_price('AAB');
778 # information price incl tax excluding allowances, charges
781 my $p = $self->pri_price('AAE');
788 # information price incl tax,allowances, charges
789 sub price_info_inclusive
{
791 my $p = $self->pri_price('AAE');
800 return $self->moa_amt('124');
803 sub availability_date
{
805 if ( exists $self->{availability_date
} ) {
806 return $self->{availability_date
};
811 # return text string representing action code
812 sub _translate_action
{
816 3 => 'change_requested',
820 24 => 'recorded', # Order accepted but a change notified
822 if ( $code && exists $action{$code} ) {
823 return $action{$code};
837 Class to abstractly handle a Line in an Edifact Transmission
841 Allows access to Edifact line elements by name
845 None documented at present
851 Called with an array ref of segments constituting the line
855 Colin Campbell <colin.campbell@ptfs-europe.com>
859 Copyright 2014,2015 PTFS-Europe Ltd
860 This program is free software, You may redistribute it under
861 under the terms of the GNU General Public License