Line 0
Link Here
|
|
|
1 |
package C4::EDI; |
2 |
|
3 |
# Copyright 2011 Mark Gavillet |
4 |
# |
5 |
# This file is part of Koha. |
6 |
# |
7 |
# Koha is free software; you can redistribute it and/or modify it under the |
8 |
# terms of the GNU General Public License as published by the Free Software |
9 |
# Foundation; either version 2 of the License, or (at your option) any later |
10 |
# version. |
11 |
# |
12 |
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
13 |
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
14 |
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
15 |
# |
16 |
# You should have received a copy of the GNU General Public License along |
17 |
# with Koha; if not, write to the Free Software Foundation, Inc., |
18 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
19 |
|
20 |
use strict; |
21 |
use warnings; |
22 |
use C4::Context; |
23 |
use C4::Acquisition; |
24 |
use Net::FTP; |
25 |
use Business::Edifact::Interchange; |
26 |
use C4::Biblio; |
27 |
use C4::Items; |
28 |
use Business::ISBN; |
29 |
use Carp; |
30 |
use parent qw(Exporter); |
31 |
|
32 |
our $VERSION = 3.09.00.53; |
33 |
our @EXPORT = qw( |
34 |
GetEDIAccounts |
35 |
GetEDIAccountDetails |
36 |
CreateEDIDetails |
37 |
UpdateEDIDetails |
38 |
LogEDIFactOrder |
39 |
LogEDIFactQuote |
40 |
DeleteEDIDetails |
41 |
GetVendorList |
42 |
GetEDIfactMessageList |
43 |
GetEDIFTPAccounts |
44 |
LogEDITransaction |
45 |
GetVendorSAN |
46 |
CreateEDIOrder |
47 |
SendEDIOrder |
48 |
SendQueuedEDIOrders |
49 |
ParseEDIQuote |
50 |
GetDiscountedPrice |
51 |
GetBudgetID |
52 |
CheckOrderItemExists |
53 |
GetBranchCode |
54 |
string35escape |
55 |
GetOrderItemInfo |
56 |
CheckVendorFTPAccountExists |
57 |
); |
58 |
|
59 |
=head1 NAME |
60 |
|
61 |
C4::EDI - Perl Module containing functions for Vendor EDI accounts and EDIfact messages |
62 |
|
63 |
=head1 VERSION |
64 |
|
65 |
Version 0.01 |
66 |
|
67 |
=head1 SYNOPSIS |
68 |
|
69 |
use C4::EDI; |
70 |
|
71 |
=head1 DESCRIPTION |
72 |
|
73 |
This module contains routines for adding, modifying and deleting EDI account details for vendors, interacting with vendor FTP sites to send/retrieve quote and order messages, formatting EDIfact orders, and parsing EDIfact quotes to baskets |
74 |
|
75 |
=head2 GetVendorList |
76 |
|
77 |
Returns a list of vendors from aqbooksellers to populate drop down select menu |
78 |
|
79 |
=cut |
80 |
|
81 |
sub GetVendorList { |
82 |
my $dbh = C4::Context->dbh; |
83 |
my $sth; |
84 |
$sth = |
85 |
$dbh->prepare("select id, name from aqbooksellers order by name asc"); |
86 |
$sth->execute(); |
87 |
my $vendorlist = $sth->fetchall_arrayref( {} ); |
88 |
return $vendorlist; |
89 |
} |
90 |
|
91 |
=head2 CreateEDIDetails |
92 |
|
93 |
Inserts a new EDI vendor FTP account |
94 |
|
95 |
=cut |
96 |
|
97 |
sub CreateEDIDetails { |
98 |
my ( $provider, $description, $host, $user, $pass, $in_dir, $san ) = @_; |
99 |
my $dbh = C4::Context->dbh; |
100 |
my $sth; |
101 |
if ($provider) { |
102 |
$sth = $dbh->prepare( |
103 |
"insert into vendor_edi_accounts (description, host, username, password, provider, in_dir, san) values (?,?,?,?,?,?,?)" |
104 |
); |
105 |
$sth->execute( $description, $host, $user, |
106 |
$pass, $provider, $in_dir, $san ); |
107 |
} |
108 |
return; |
109 |
} |
110 |
|
111 |
=head2 GetEDIAccounts |
112 |
|
113 |
Returns all vendor FTP accounts |
114 |
|
115 |
=cut |
116 |
|
117 |
sub GetEDIAccounts { |
118 |
my $dbh = C4::Context->dbh; |
119 |
my $sth; |
120 |
$sth = $dbh->prepare( |
121 |
"select vendor_edi_accounts.id, aqbooksellers.id as providerid, aqbooksellers.name as vendor, vendor_edi_accounts.description, vendor_edi_accounts.last_activity from vendor_edi_accounts inner join aqbooksellers on vendor_edi_accounts.provider = aqbooksellers.id order by aqbooksellers.name asc" |
122 |
); |
123 |
$sth->execute(); |
124 |
my $ediaccounts = $sth->fetchall_arrayref( {} ); |
125 |
return $ediaccounts; |
126 |
} |
127 |
|
128 |
=head2 DeleteEDIDetails |
129 |
|
130 |
Remove a vendor's FTP account |
131 |
|
132 |
=cut |
133 |
|
134 |
sub DeleteEDIDetails { |
135 |
my ($id) = @_; |
136 |
my $dbh = C4::Context->dbh; |
137 |
my $sth; |
138 |
if ($id) { |
139 |
$sth = $dbh->prepare("delete from vendor_edi_accounts where id=?"); |
140 |
$sth->execute($id); |
141 |
} |
142 |
return; |
143 |
} |
144 |
|
145 |
=head2 UpdateEDIDetails |
146 |
|
147 |
Update a vendor's FTP account |
148 |
|
149 |
=cut |
150 |
|
151 |
sub UpdateEDIDetails { |
152 |
my ( $editid, $description, $host, $user, $pass, $provider, $in_dir, $san ) |
153 |
= @_; |
154 |
my $dbh = C4::Context->dbh; |
155 |
if ($editid) { |
156 |
my $sth = $dbh->prepare( |
157 |
"update vendor_edi_accounts set description=?, host=?, username=?, password=?, provider=?, in_dir=?, san=? where id=?" |
158 |
); |
159 |
$sth->execute( $description, $host, $user, $pass, $provider, $in_dir, |
160 |
$san, $editid ); |
161 |
} |
162 |
return; |
163 |
} |
164 |
|
165 |
=head2 LogEDIFactOrder |
166 |
|
167 |
Updates or inserts to the edifact_messages table when processing an order and assigns a status and basket number |
168 |
|
169 |
=cut |
170 |
|
171 |
sub LogEDIFactOrder { |
172 |
my ( $provider, $status, $basketno ) = @_; |
173 |
my $dbh = C4::Context->dbh; |
174 |
my $key; |
175 |
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time); |
176 |
$year += 1900; |
177 |
$mon += 1; |
178 |
my $date_sent = $year . '-' . $mon . "-$mday"; |
179 |
my $sth = $dbh->prepare( |
180 |
"select edifact_messages.key from edifact_messages where basketno=? and provider=?" |
181 |
); |
182 |
$sth->execute( $basketno, $provider ); |
183 |
|
184 |
#my $key=$sth->fetchrow_array(); |
185 |
while ( my @row = $sth->fetchrow_array() ) { |
186 |
$key = $row[0]; |
187 |
} |
188 |
if ($key) { |
189 |
$sth = $dbh->prepare( |
190 |
"update edifact_messages set date_sent=?, status=? where edifact_messages.key=?" |
191 |
); |
192 |
$sth->execute( $date_sent, $status, $key ); |
193 |
} |
194 |
else { |
195 |
$sth = $dbh->prepare( |
196 |
"insert into edifact_messages (message_type,date_sent,provider,status,basketno) values (?,?,?,?,?)" |
197 |
); |
198 |
$sth->execute( 'ORDER', $date_sent, $provider, $status, $basketno ); |
199 |
} |
200 |
return; |
201 |
} |
202 |
|
203 |
=head2 LogEDIFactOrder |
204 |
|
205 |
Updates or inserts to the edifact_messages table when processing a quote and assigns a status and basket number |
206 |
|
207 |
=cut |
208 |
|
209 |
sub LogEDIFactQuote { |
210 |
my ( $provider, $status, $basketno, $key ) = @_; |
211 |
my $dbh = C4::Context->dbh; |
212 |
my $sth; |
213 |
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time); |
214 |
$year = 1900 + $year; |
215 |
$mon = 1 + $mon; |
216 |
my $date_sent = $year . "-" . $mon . "-$mday"; |
217 |
if ( $key != 0 ) { |
218 |
$sth = $dbh->prepare( |
219 |
"update edifact_messages set date_sent=?, status=?, basketno=? where edifact_messages.key=?" |
220 |
); |
221 |
$sth->execute( $date_sent, $status, $basketno, $key ); |
222 |
} |
223 |
else { |
224 |
$sth = $dbh->prepare( |
225 |
"insert into edifact_messages (message_type,date_sent,provider,status,basketno) values (?,?,?,?,?)" |
226 |
); |
227 |
$sth->execute( 'QUOTE', $date_sent, $provider, $status, $basketno ); |
228 |
$key = |
229 |
$dbh->last_insert_id( undef, undef, qw(edifact_messages key), undef ); |
230 |
} |
231 |
return $key; |
232 |
} |
233 |
|
234 |
=head2 GetEDIAccountDetails |
235 |
|
236 |
Returns FTP account details for a given vendor |
237 |
|
238 |
=cut |
239 |
|
240 |
sub GetEDIAccountDetails { |
241 |
my ($id) = @_; |
242 |
my $dbh = C4::Context->dbh; |
243 |
my $sth; |
244 |
if ($id) { |
245 |
$sth = $dbh->prepare("select * from vendor_edi_accounts where id=?"); |
246 |
$sth->execute($id); |
247 |
my $edi_details = $sth->fetchrow_hashref; |
248 |
return $edi_details; |
249 |
} |
250 |
return; |
251 |
} |
252 |
|
253 |
=head2 GetEDIfactMessageList |
254 |
|
255 |
Returns a list of edifact_messages that have been processed, including the type (quote/order) and status |
256 |
|
257 |
=cut |
258 |
|
259 |
sub GetEDIfactMessageList { |
260 |
my $dbh = C4::Context->dbh; |
261 |
my $sth; |
262 |
$sth = $dbh->prepare( |
263 |
"select edifact_messages.key, edifact_messages.message_type, DATE_FORMAT(edifact_messages.date_sent,'%d/%m/%Y') as date_sent, aqbooksellers.id as providerid, aqbooksellers.name as providername, edifact_messages.status, edifact_messages.basketno from edifact_messages inner join aqbooksellers on edifact_messages.provider = aqbooksellers.id order by edifact_messages.date_sent desc, edifact_messages.key desc" |
264 |
); |
265 |
$sth->execute(); |
266 |
my $messagelist = $sth->fetchall_arrayref( {} ); |
267 |
return $messagelist; |
268 |
} |
269 |
|
270 |
=head2 GetEDIFTPAccounts |
271 |
|
272 |
Returns all vendor FTP accounts. Used when retrieving quotes messages overnight |
273 |
|
274 |
=cut |
275 |
|
276 |
sub GetEDIFTPAccounts { |
277 |
my $dbh = C4::Context->dbh; |
278 |
my $sth; |
279 |
$sth = $dbh->prepare( |
280 |
"select id, host, username, password, provider, in_dir from vendor_edi_accounts order by id asc" |
281 |
); |
282 |
$sth->execute(); |
283 |
my $ftpaccounts = $sth->fetchall_arrayref( {} ); |
284 |
return $ftpaccounts; |
285 |
} |
286 |
|
287 |
=head2 LogEDITransaction |
288 |
|
289 |
Updates the timestamp for a given vendor FTP account whenever there is activity |
290 |
|
291 |
=cut |
292 |
|
293 |
sub LogEDITransaction { |
294 |
my $id = shift; |
295 |
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time); |
296 |
$year = 1900 + $year; |
297 |
$mon = 1 + $mon; |
298 |
my $datestamp = $year . "/" . $mon . "/$mday"; |
299 |
my $dbh = C4::Context->dbh; |
300 |
my $sth; |
301 |
$sth = $dbh->prepare( |
302 |
"update vendor_edi_accounts set last_activity=? where id=?"); |
303 |
$sth->execute( $datestamp, $id ); |
304 |
return; |
305 |
} |
306 |
|
307 |
=head2 GetVendorSAN |
308 |
|
309 |
Returns the stored SAN number for a given vendor |
310 |
|
311 |
=cut |
312 |
|
313 |
sub GetVendorSAN { |
314 |
my $booksellerid = shift; |
315 |
my $dbh = C4::Context->dbh; |
316 |
my $san; |
317 |
my $sth = |
318 |
$dbh->prepare("select san from vendor_edi_accounts where provider=?"); |
319 |
$sth->execute($booksellerid); |
320 |
while ( my @result = $sth->fetchrow_array() ) { |
321 |
$san = $result[0]; |
322 |
} |
323 |
return $san; |
324 |
} |
325 |
|
326 |
=head2 CreateEDIOrder |
327 |
|
328 |
Formats an EDIfact order message from a given basket and stores as a file on the server |
329 |
|
330 |
=cut |
331 |
|
332 |
sub CreateEDIOrder { |
333 |
my ( $basketno, $booksellerid ) = @_; |
334 |
my @datetime = localtime(time); |
335 |
my $longyear = $datetime[5] + 1900; |
336 |
my $shortyear = sprintf '%02d', $datetime[5] - 100; |
337 |
my $date = sprintf '%02d%02d', $datetime[4] + 1, $datetime[3]; |
338 |
my $hourmin = sprintf '%02d%02d', $datetime[2], $datetime[1]; |
339 |
my $year = $datetime[5] - 100; |
340 |
my $month = sprintf '%02d', $datetime[4] + 1; |
341 |
my $linecount = 0; |
342 |
my $filename = "ediorder_$basketno.CEP"; |
343 |
my $exchange = int( rand(99999999999999) ); |
344 |
my $ref = int( rand(99999999999999) ); |
345 |
my $san = GetVendorSAN($booksellerid); |
346 |
my $message_type = GetMessageType($basketno); |
347 |
my $output_file = C4::Context->config('intranetdir'); |
348 |
$output_file .= "/misc/edi_files/$filename"; |
349 |
|
350 |
open my $fh, '>', $output_file |
351 |
or croak "Unable to create $output_file : $!"; |
352 |
|
353 |
print $fh "UNA:+.? '"; # print opening header |
354 |
print $fh "UNB+UNOC:2+" |
355 |
. C4::Context->preference("EDIfactEAN") |
356 |
. ":14+$san:31B+$shortyear$date:$hourmin+" |
357 |
. $exchange |
358 |
. "++ORDERS+++EANCOM'" |
359 |
; # print identifying EANs/SANs, date/time, exchange reference number |
360 |
print $fh "UNH+" . $ref |
361 |
. "+ORDERS:D:96A:UN:EAN008'"; # print message reference number |
362 |
|
363 |
if ( $message_type eq 'QUOTE' ) { |
364 |
print $fh "BGM+22V+" |
365 |
. $basketno |
366 |
. "+9'"; # print order number and quote confirmation ref |
367 |
} |
368 |
else { |
369 |
print $fh "BGM+220+" . $basketno . "+9'"; # print order number |
370 |
} |
371 |
print $fh "DTM+137:$longyear$date:102'"; # print date of message |
372 |
print $fh "NAD+BY+" |
373 |
. C4::Context->preference("EDIfactEAN") |
374 |
. "::9'"; # print buyer EAN |
375 |
print $fh "NAD+SU+" . $san . "::31B'"; # print vendor SAN |
376 |
print $fh "NAD+SU+" . $booksellerid . "::92'"; # print internal ID |
377 |
|
378 |
# get items from basket |
379 |
my @results = GetOrders($basketno); |
380 |
foreach my $item (@results) { |
381 |
$linecount++; |
382 |
my $price; |
383 |
my $title = string35escape( escape( $item->{title} ) ); |
384 |
my $author = string35escape( escape( $item->{author} ) ); |
385 |
my $publisher = string35escape( escape( $item->{publishercode} ) ); |
386 |
$price = sprintf "%.2f", $item->{listprice}; |
387 |
my $isbn; |
388 |
if ( length( $item->{isbn} ) == 10 |
389 |
|| substr( $item->{isbn}, 0, 3 ) eq "978" |
390 |
|| index( $item->{isbn}, "|" ) != -1 ) |
391 |
{ |
392 |
$isbn = cleanisbn( $item->{isbn} ); |
393 |
$isbn = Business::ISBN->new($isbn); |
394 |
if ($isbn) { |
395 |
if ( $isbn->is_valid ) { |
396 |
$isbn = ( $isbn->as_isbn13 )->isbn; |
397 |
} |
398 |
else { |
399 |
$isbn = "0"; |
400 |
} |
401 |
} |
402 |
else { |
403 |
$isbn = 0; |
404 |
} |
405 |
} |
406 |
else { |
407 |
$isbn = $item->{isbn}; |
408 |
} |
409 |
my $copyrightdate = escape( $item->{publicationyear} ); |
410 |
my $quantity = escape( $item->{quantity} ); |
411 |
my $ordernumber = escape( $item->{ordernumber} ); |
412 |
my $notes; |
413 |
if ( $item->{notes} ) { |
414 |
$notes = $item->{notes}; |
415 |
$notes =~ s/[\r\n]+//g; |
416 |
$notes = string35escape( escape($notes) ); |
417 |
} |
418 |
|
419 |
my ( $branchcode, $callnumber, $itype, $lsqccode, $fund ) = |
420 |
GetOrderItemInfo( $item->{'ordernumber'} ); |
421 |
$callnumber = escape($callnumber); |
422 |
|
423 |
print $fh "LIN+$linecount++" . $isbn . ":EN'"; # line number, isbn |
424 |
print $fh "PIA+5+" . $isbn |
425 |
. ":IB'"; # isbn as main product identification |
426 |
print $fh "IMD+L+050+:::$title'"; # title |
427 |
print $fh "IMD+L+009+:::$author'"; # full name of author |
428 |
print $fh "IMD+L+109+:::$publisher'"; # publisher |
429 |
print $fh "IMD+L+170+:::$copyrightdate'"; # date of publication |
430 |
print $fh "IMD+L+220+:::O'"; # binding (e.g. PB) (O if not specified) |
431 |
if ( $callnumber ne '' ) { |
432 |
print $fh "IMD+L+230+:::$callnumber'"; # shelfmark |
433 |
} |
434 |
print $fh "QTY+21:$quantity'"; # quantity |
435 |
if ( $message_type ne 'QUOTE' && $quantity > 1 ) { |
436 |
print $fh "GIR+001+$quantity:LQT+$branchcode:LLO+$fund:LFN+" |
437 |
; # branchcode, fund code |
438 |
} |
439 |
else { |
440 |
print $fh |
441 |
"GIR+001+$branchcode:LLO+$fund:LFN+"; # branchcode, fund code |
442 |
} |
443 |
if ( $callnumber ne '' ) { |
444 |
print $fh "$callnumber:LCL+"; # shelfmark |
445 |
} |
446 |
print $fh $itype . ":LST+$lsqccode:LSQ'"; # stock category, sequence |
447 |
if ($notes) { |
448 |
print $fh "FTX+LIN+++:::$notes'"; |
449 |
} |
450 |
###REQUEST ORDERS TO REVISIT |
451 |
#if ($message_type ne 'QUOTE') |
452 |
#{ |
453 |
# print $fh "FTX+LIN++$linecount:10B:28'"; # freetext ** used for request orders to denote priority (to revisit) |
454 |
#} |
455 |
print $fh "PRI+AAB:$price'"; # price per item |
456 |
print $fh "CUX+2:GBP:9'"; # currency (GBP) |
457 |
print $fh "RFF+LI:$ordernumber'"; # Local order number |
458 |
if ( $message_type eq 'QUOTE' ) { |
459 |
print $fh "RFF+QLI:" |
460 |
. $item->{booksellerinvoicenumber} |
461 |
. q{'}; # If QUOTE confirmation, include booksellerinvoicenumber |
462 |
} |
463 |
} |
464 |
print $fh "UNS+S'"; # print summary section header |
465 |
print $fh "CNT+2:$linecount'"; # print number of line items in the message |
466 |
my $segments = ( ( $linecount * 13 ) + 9 ); |
467 |
print $fh "UNT+$segments+" |
468 |
. $ref . "'" |
469 |
; # No. of segments in message (UNH+UNT elements included, UNA, UNB, UNZ excluded) |
470 |
# Message ref number |
471 |
print $fh "UNZ+1+" . $exchange . "'\n"; # Exchange ref number |
472 |
|
473 |
close $fh; |
474 |
|
475 |
LogEDIFactOrder( $booksellerid, 'Queued', $basketno ); |
476 |
|
477 |
return $filename; |
478 |
|
479 |
} |
480 |
|
481 |
sub GetMessageType { |
482 |
my $basketno = shift; |
483 |
my $dbh = C4::Context->dbh; |
484 |
my $sth; |
485 |
my $message_type; |
486 |
my @row; |
487 |
$sth = $dbh->prepare( |
488 |
"select message_type from edifact_messages where basketno=?"); |
489 |
$sth->execute($basketno); |
490 |
while ( @row = $sth->fetchrow_array() ) { |
491 |
$message_type = $row[0]; |
492 |
} |
493 |
return $message_type; |
494 |
} |
495 |
|
496 |
sub cleanisbn { |
497 |
my $isbn = shift; |
498 |
if ($isbn) { |
499 |
my $i = index( $isbn, '(' ); |
500 |
if ( $i > 1 ) { |
501 |
$isbn = substr( $isbn, 0, ( $i - 1 ) ); |
502 |
} |
503 |
if ( index( $isbn, "|" ) != -1 ) { |
504 |
my @isbns = split( /\|/, $isbn ); |
505 |
$isbn = $isbns[0]; |
506 |
|
507 |
#print "0: ".$isbns[0]."\n"; |
508 |
} |
509 |
$isbn = escape($isbn); |
510 |
$isbn =~ s/^\s+//; |
511 |
$isbn =~ s/\s+$//; |
512 |
return $isbn; |
513 |
} |
514 |
return; |
515 |
} |
516 |
|
517 |
sub escape { |
518 |
my $string = shift; |
519 |
if ($string) { |
520 |
$string =~ s/\?/\?\?/g; |
521 |
$string =~ s/\'/\?\'/g; |
522 |
$string =~ s/\:/\?\:/g; |
523 |
$string =~ s/\+/\?\+/g; |
524 |
return $string; |
525 |
} |
526 |
return; |
527 |
} |
528 |
|
529 |
=head2 GetBranchCode |
530 |
|
531 |
Return branchcode for an order when formatting an EDIfact order message |
532 |
|
533 |
=cut |
534 |
|
535 |
sub GetBranchCode { |
536 |
my $biblioitemnumber = shift; |
537 |
my $dbh = C4::Context->dbh; |
538 |
my $sth; |
539 |
my $branchcode; |
540 |
my @row; |
541 |
$sth = |
542 |
$dbh->prepare("select homebranch from items where biblioitemnumber=?"); |
543 |
$sth->execute($biblioitemnumber); |
544 |
while ( @row = $sth->fetchrow_array() ) { |
545 |
$branchcode = $row[0]; |
546 |
} |
547 |
return $branchcode; |
548 |
} |
549 |
|
550 |
=head2 SendEDIOrder |
551 |
|
552 |
Transfers an EDIfact order message to the relevant vendor's FTP site |
553 |
|
554 |
=cut |
555 |
|
556 |
sub SendEDIOrder { |
557 |
my ( $basketno, $booksellerid ) = @_; |
558 |
my $newerr; |
559 |
my $result; |
560 |
|
561 |
# check edi order file exists |
562 |
my $edi_files = C4::Context->config('intranetdir'); |
563 |
$edi_files .= '/misc/edi_files/'; |
564 |
if ( -e "${edi_files}ediorder_$basketno.CEP" ) { |
565 |
my $dbh = C4::Context->dbh; |
566 |
my $sth; |
567 |
$sth = $dbh->prepare( |
568 |
"select id, host, username, password, provider, in_dir from vendor_edi_accounts where provider=?" |
569 |
); |
570 |
$sth->execute($booksellerid); |
571 |
my $ftpaccount = $sth->fetchrow_hashref; |
572 |
|
573 |
#check vendor edi account exists |
574 |
if ($ftpaccount) { |
575 |
|
576 |
# connect to ftp account |
577 |
my $ftp = Net::FTP->new( $ftpaccount->{host}, Timeout => 10 ) |
578 |
or $newerr = 1; |
579 |
if ( !$newerr ) { |
580 |
$newerr = 0; |
581 |
|
582 |
# login |
583 |
$ftp->login( "$ftpaccount->{username}", |
584 |
"$ftpaccount->{password}" ) |
585 |
or $newerr = 1; |
586 |
$ftp->quit if $newerr; |
587 |
if ( !$newerr ) { |
588 |
|
589 |
# cd to directory |
590 |
$ftp->cwd("$ftpaccount->{in_dir}") or $newerr = 1; |
591 |
$ftp->quit if $newerr; |
592 |
|
593 |
# put file |
594 |
if ( !$newerr ) { |
595 |
$newerr = 0; |
596 |
$ftp->put("${edi_files}ediorder_$basketno.CEP") |
597 |
or $newerr = 1; |
598 |
$ftp->quit if $newerr; |
599 |
if ( !$newerr ) { |
600 |
$result = |
601 |
"File: ediorder_$basketno.CEP transferred successfully"; |
602 |
$ftp->quit; |
603 |
unlink "${edi_files}ediorder_$basketno.CEP"; |
604 |
LogEDITransaction( $ftpaccount->{id} ); |
605 |
LogEDIFactOrder( $booksellerid, 'Sent', $basketno ); |
606 |
return $result; |
607 |
} |
608 |
else { |
609 |
$result = |
610 |
"Could not transfer the file ${edi_files}ediorder_$basketno.CEP to $ftpaccount->{host}: $_"; |
611 |
FTPError($result); |
612 |
LogEDIFactOrder( $booksellerid, 'Failed', |
613 |
$basketno ); |
614 |
return $result; |
615 |
} |
616 |
} |
617 |
else { |
618 |
$result = |
619 |
"Cannot get remote directory ($ftpaccount->{in_dir}) on $ftpaccount->{host}"; |
620 |
FTPError($result); |
621 |
LogEDIFactOrder( $booksellerid, 'Failed', $basketno ); |
622 |
return $result; |
623 |
} |
624 |
} |
625 |
else { |
626 |
$result = "Cannot log in to $ftpaccount->{host}: $!"; |
627 |
FTPError($result); |
628 |
LogEDIFactOrder( $booksellerid, 'Failed', $basketno ); |
629 |
return $result; |
630 |
} |
631 |
} |
632 |
else { |
633 |
$result = |
634 |
"Cannot make an FTP connection to $ftpaccount->{host}: $!"; |
635 |
FTPError($result); |
636 |
LogEDIFactOrder( $booksellerid, 'Failed', $basketno ); |
637 |
return $result; |
638 |
} |
639 |
} |
640 |
else { |
641 |
$result = |
642 |
"Vendor ID: $booksellerid does not have a current EDIfact FTP account"; |
643 |
FTPError($result); |
644 |
LogEDIFactOrder( $booksellerid, 'Failed', $basketno ); |
645 |
return $result; |
646 |
} |
647 |
} |
648 |
else { |
649 |
$result = "There is no EDIfact order for this basket"; |
650 |
return $result; |
651 |
} |
652 |
} |
653 |
|
654 |
sub FTPError { |
655 |
my $error = shift; |
656 |
my $log_file = C4::Context->config('intranetdir'); |
657 |
$log_file .= '/misc/edi_files/edi_ftp_error.log'; |
658 |
open my $log_fh, '>>', $log_file |
659 |
or croak "Could not open $log_file: $!"; |
660 |
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime(time); |
661 |
printf $log_fh "%4d-%02d-%02d %02d:%02d:%02d\n-----\n", |
662 |
$year + 1900, $mon + 1, $mday, $hour, $min, $sec; |
663 |
print $log_fh "$error\n\n"; |
664 |
close $log_fh; |
665 |
return; |
666 |
} |
667 |
|
668 |
=head2 SendQueuedEDIOrders |
669 |
|
670 |
Sends all EDIfact orders that are held in the Queued |
671 |
|
672 |
=cut |
673 |
|
674 |
sub SendQueuedEDIOrders { |
675 |
my $dbh = C4::Context->dbh; |
676 |
my @orders; |
677 |
my $sth = $dbh->prepare( |
678 |
q|select basketno, provider from edifact_messages where status='Queued'| |
679 |
); |
680 |
$sth->execute(); |
681 |
while ( @orders = $sth->fetchrow_array() ) { |
682 |
SendEDIOrder( $orders[0], $orders[1] ); |
683 |
} |
684 |
return; |
685 |
} |
686 |
|
687 |
=head2 ParseEDIQuote |
688 |
|
689 |
Uses Business::Edifact::Interchange to parse a stored EDIfact quote message, creates basket, biblios, biblioitems, and items |
690 |
|
691 |
=cut |
692 |
|
693 |
sub ParseEDIQuote { |
694 |
my ( $filename, $booksellerid ) = @_; |
695 |
my $ordernumber; |
696 |
my $basketno; |
697 |
my $ParseEDIQuoteItem; |
698 |
|
699 |
#print "file: $filename\n"; |
700 |
my $edi = Business::Edifact::Interchange->new; |
701 |
my $path = C4::Context->config('intranetdir'); |
702 |
$path .= '/misc/edi_files/'; |
703 |
$edi->parse_file("$path$filename"); |
704 |
my $messages = $edi->messages(); |
705 |
my $msg_cnt = @{$messages}; |
706 |
|
707 |
#print "messages: $msg_cnt\n"; |
708 |
#print "type: ".$messages->[0]->type()."\n"; |
709 |
#print "date: ".$messages->[0]->date_of_message()."\n"; |
710 |
|
711 |
# create default edifact_messages entry |
712 |
my $messagekey = LogEDIFactQuote( $booksellerid, 'Failed', 0, 0 ); |
713 |
|
714 |
#create basket |
715 |
if ( $msg_cnt > 0 && $booksellerid ) { |
716 |
$basketno = NewBasket( $booksellerid, 0, $filename, '', '', '' ); |
717 |
} |
718 |
|
719 |
$ParseEDIQuoteItem = sub { |
720 |
my ( $item, $gir, $bookseller_id ) = @_; |
721 |
my $relnos = $item->{related_numbers}; |
722 |
my $author = $item->author_surname . ", " . $item->author_firstname; |
723 |
|
724 |
my $ecost = |
725 |
GetDiscountedPrice( $bookseller_id, $item->{price}->{price} ); |
726 |
|
727 |
my $ftxlin; |
728 |
my $ftxlno; |
729 |
if ( $item->{free_text}->{qualifier} eq "LIN" ) { |
730 |
$ftxlin = $item->{free_text}->{text}; |
731 |
} |
732 |
if ( $item->{free_text}->{qualifier} eq "LNO" ) { |
733 |
$ftxlno = $item->{free_text}->{text}; |
734 |
} |
735 |
|
736 |
my ( $llo, $lfn, $lsq, $lst, $lfs, $lcl, $id ); |
737 |
my $relcount = 0; |
738 |
foreach my $rel ( @{$relnos} ) { |
739 |
if ( $rel->{id} == ( $gir + 1 ) ) { |
740 |
if ( $item->{related_numbers}->[$relcount]->{LLO}->[0] ) { |
741 |
$llo = $item->{related_numbers}->[$relcount]->{LLO}->[0]; |
742 |
} |
743 |
if ( $item->{related_numbers}->[$relcount]->{LFN}->[0] ) { |
744 |
$lfn = $item->{related_numbers}->[$relcount]->{LFN}->[0]; |
745 |
} |
746 |
if ( $item->{related_numbers}->[$relcount]->{LSQ}->[0] ) { |
747 |
$lsq = $item->{related_numbers}->[$relcount]->{LSQ}->[0]; |
748 |
} |
749 |
if ( $item->{related_numbers}->[$relcount]->{LST}->[0] ) { |
750 |
$lst = $item->{related_numbers}->[$relcount]->{LST}->[0]; |
751 |
} |
752 |
if ( $item->{related_numbers}->[$relcount]->{LFS}->[0] ) { |
753 |
$lfs = $item->{related_numbers}->[$relcount]->{LFS}->[0]; |
754 |
} |
755 |
if ( $item->{related_numbers}->[$relcount]->{LCL}->[0] ) { |
756 |
$lcl = $item->{related_numbers}->[$relcount]->{LCL}->[0]; |
757 |
} |
758 |
if ( $item->{related_numbers}->[$relcount]->{id} ) { |
759 |
$id = $item->{related_numbers}->[$relcount]->{id}; |
760 |
} |
761 |
} |
762 |
$relcount++; |
763 |
} |
764 |
|
765 |
my $lclnote; |
766 |
if ( !$lst ) { |
767 |
$lst = uc( $item->item_format ); |
768 |
} |
769 |
if ( !$lcl ) { |
770 |
$lcl = $item->shelfmark; |
771 |
} |
772 |
else { |
773 |
( $lcl, $lclnote ) = DawsonsLCL($lcl); |
774 |
} |
775 |
if ($lfs) { |
776 |
$lcl .= " $lfs"; |
777 |
} |
778 |
|
779 |
my $budget_id = GetBudgetID($lfn); |
780 |
|
781 |
#Uncomment section below to define a default budget_id if there is no match |
782 |
#if (!defined $budget_id) |
783 |
#{ |
784 |
# $budget_id=0; |
785 |
#} |
786 |
|
787 |
# create biblio record |
788 |
my $bib_record = TransformKohaToMarc( |
789 |
{ |
790 |
'biblio.title' => $item->title, |
791 |
'biblio.author' => $author ? $author : q{}, |
792 |
'biblio.seriestitle' => q{}, |
793 |
'biblioitems.isbn' => $item->{item_number} |
794 |
? $item->{item_number} |
795 |
: q{}, |
796 |
'biblioitems.publishercode' => $item->publisher |
797 |
? $item->publisher |
798 |
: q{}, |
799 |
'biblioitems.publicationyear' => $item->date_of_publication |
800 |
? $item->date_of_publication |
801 |
: q{}, |
802 |
'biblio.copyrightdate' => $item->date_of_publication |
803 |
? $item->date_of_publication |
804 |
: q{}, |
805 |
'biblioitems.itemtype' => uc( $item->item_format ), |
806 |
'biblioitems.cn_source' => 'ddc', |
807 |
'items.cn_source' => 'ddc', |
808 |
'items.notforloan' => -1, |
809 |
|
810 |
#"items.ccode" => $lsq, |
811 |
'items.location' => $lsq, |
812 |
'items.homebranch' => $llo, |
813 |
'items.holdingbranch' => $llo, |
814 |
'items.booksellerid' => $bookseller_id, |
815 |
'items.price' => $item->{price}->{price}, |
816 |
'items.replacementprice' => $item->{price}->{price}, |
817 |
'items.itemcallnumber' => $lcl, |
818 |
'items.itype' => $lst, |
819 |
'items.cn_sort' => q{}, |
820 |
} |
821 |
); |
822 |
|
823 |
#check if item already exists in catalogue |
824 |
my $biblionumber; |
825 |
my $bibitemnumber; |
826 |
( $biblionumber, $bibitemnumber ) = |
827 |
CheckOrderItemExists( $item->{item_number} ); |
828 |
|
829 |
if ( !defined $biblionumber ) { |
830 |
|
831 |
# create the record in catalogue, with framework '' |
832 |
( $biblionumber, $bibitemnumber ) = AddBiblio( $bib_record, q{} ); |
833 |
} |
834 |
|
835 |
my $ordernote; |
836 |
if ($lclnote) { |
837 |
$ordernote = $lclnote; |
838 |
} |
839 |
if ($ftxlno) { |
840 |
$ordernote = $ftxlno; |
841 |
} |
842 |
if ($ftxlin) { |
843 |
$ordernote = $ftxlin; |
844 |
} |
845 |
|
846 |
my %orderinfo = ( |
847 |
basketno => $basketno, |
848 |
ordernumber => q{}, |
849 |
subscription => 'no', |
850 |
uncertainprice => 0, |
851 |
biblionumber => $biblionumber, |
852 |
title => $item->title, |
853 |
quantity => 1, |
854 |
biblioitemnumber => $bibitemnumber, |
855 |
rrp => $item->{price}->{price}, |
856 |
ecost => $ecost, |
857 |
sort1 => q{}, |
858 |
sort2 => q{}, |
859 |
booksellerinvoicenumber => $item->{item_reference}[0][1], |
860 |
listprice => $item->{price}->{price}, |
861 |
branchcode => $llo, |
862 |
budget_id => $budget_id, |
863 |
notes => $ordernote, |
864 |
); |
865 |
|
866 |
my $orderinfo = \%orderinfo; |
867 |
|
868 |
my ( $retbasketno, $ordernumber ) = NewOrder($orderinfo); |
869 |
|
870 |
# now, add items if applicable |
871 |
if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) { |
872 |
my $itemnumber; |
873 |
( $biblionumber, $bibitemnumber, $itemnumber ) = |
874 |
AddItemFromMarc( $bib_record, $biblionumber ); |
875 |
NewOrderItem( $itemnumber, $ordernumber ); |
876 |
} |
877 |
}; |
878 |
|
879 |
for ( my $count = 0 ; $count < $msg_cnt ; $count++ ) { |
880 |
my $items = $messages->[$count]->items(); |
881 |
my $ref_num = $messages->[$count]->{ref_num}; |
882 |
|
883 |
foreach my $item ( @{$items} ) { |
884 |
for ( my $i = 0 ; $i < $item->{quantity} ; $i++ ) { |
885 |
&$ParseEDIQuoteItem( $item, $i, $booksellerid, $basketno ); |
886 |
} |
887 |
} |
888 |
} |
889 |
|
890 |
# update edifact_messages entry |
891 |
$messagekey = |
892 |
LogEDIFactQuote( $booksellerid, 'Received', $basketno, $messagekey ); |
893 |
return 1; |
894 |
|
895 |
} |
896 |
|
897 |
=head2 GetDiscountedPrice |
898 |
|
899 |
Returns the discounted price for an order based on the discount rate for a given vendor |
900 |
|
901 |
=cut |
902 |
|
903 |
sub GetDiscountedPrice { |
904 |
my ( $booksellerid, $price ) = @_; |
905 |
my $dbh = C4::Context->dbh; |
906 |
my $sth; |
907 |
my @discount; |
908 |
my $ecost; |
909 |
my $percentage; |
910 |
$sth = $dbh->prepare(q|select discount from aqbooksellers where id=?|); |
911 |
$sth->execute($booksellerid); |
912 |
|
913 |
while ( @discount = $sth->fetchrow_array() ) { |
914 |
$percentage = $discount[0]; |
915 |
} |
916 |
$ecost = ( $price - ( ( $percentage * $price ) / 100 ) ); |
917 |
return $ecost; |
918 |
} |
919 |
|
920 |
=head2 DawsonsLCL |
921 |
|
922 |
Checks for a call number encased by asterisks. If found, returns call number as $lcl and string with |
923 |
asterisks as $lclnote to go into FTX field enabling spine label creation by Dawsons bookseller |
924 |
|
925 |
=cut |
926 |
|
927 |
sub DawsonsLCL { |
928 |
my $lcl = shift; |
929 |
my $lclnote; |
930 |
my $f = index( $lcl, '*' ); |
931 |
my $l = rindex( $lcl, '*' ); |
932 |
if ( $f == 0 && $l == ( length($lcl) - 1 ) ) { |
933 |
$lclnote = $lcl; |
934 |
$lcl =~ s/\*//g; |
935 |
} |
936 |
return ( $lcl, $lclnote ); |
937 |
} |
938 |
|
939 |
=head2 GetBudgetID |
940 |
|
941 |
Returns the budget_id for a given budget_code |
942 |
|
943 |
=cut |
944 |
|
945 |
sub GetBudgetID { |
946 |
my $fundcode = shift; |
947 |
my $dbh = C4::Context->dbh; |
948 |
my $sth; |
949 |
my @funds; |
950 |
my $ecost; |
951 |
my $budget_id; |
952 |
$sth = $dbh->prepare("select budget_id from aqbudgets where budget_code=?"); |
953 |
$sth->execute($fundcode); |
954 |
|
955 |
while ( @funds = $sth->fetchrow_array() ) { |
956 |
$budget_id = $funds[0]; |
957 |
} |
958 |
return $budget_id; |
959 |
} |
960 |
|
961 |
=head2 CheckOrderItemExists |
962 |
|
963 |
Checks to see if a biblio record already exists in the catalogue when parsing a quotes message |
964 |
Converts 10-13 digit ISBNs and vice-versa if an initial match is not found |
965 |
|
966 |
=cut |
967 |
|
968 |
sub CheckOrderItemExists { |
969 |
my $isbn = shift; |
970 |
my $dbh = C4::Context->dbh; |
971 |
my $sth; |
972 |
my @matches; |
973 |
my $biblionumber; |
974 |
my $bibitemnumber; |
975 |
$sth = $dbh->prepare( |
976 |
"select biblionumber, biblioitemnumber from biblioitems where isbn=?"); |
977 |
$sth->execute($isbn); |
978 |
|
979 |
while ( @matches = $sth->fetchrow_array() ) { |
980 |
$biblionumber = $matches[0]; |
981 |
$bibitemnumber = $matches[1]; |
982 |
} |
983 |
if ($biblionumber) { |
984 |
return $biblionumber, $bibitemnumber; |
985 |
} |
986 |
else { |
987 |
$isbn = cleanisbn($isbn); |
988 |
if ( length($isbn) == 10 ) { |
989 |
$isbn = Business::ISBN->new($isbn); |
990 |
if ($isbn) { |
991 |
if ( $isbn->is_valid ) { |
992 |
$isbn = ( $isbn->as_isbn13 )->isbn; |
993 |
$sth->execute($isbn); |
994 |
while ( @matches = $sth->fetchrow_array() ) { |
995 |
$biblionumber = $matches[0]; |
996 |
$bibitemnumber = $matches[1]; |
997 |
} |
998 |
} |
999 |
} |
1000 |
} |
1001 |
elsif ( length($isbn) == 13 ) { |
1002 |
$isbn = Business::ISBN->new($isbn); |
1003 |
if ($isbn) { |
1004 |
if ( $isbn->is_valid ) { |
1005 |
$isbn = ( $isbn->as_isbn10 )->isbn; |
1006 |
$sth->execute($isbn); |
1007 |
while ( @matches = $sth->fetchrow_array() ) { |
1008 |
$biblionumber = $matches[0]; |
1009 |
$bibitemnumber = $matches[1]; |
1010 |
} |
1011 |
} |
1012 |
} |
1013 |
} |
1014 |
return $biblionumber, $bibitemnumber; |
1015 |
} |
1016 |
} |
1017 |
|
1018 |
sub string35escape { |
1019 |
my $string = shift; |
1020 |
my $colon_string; |
1021 |
my @sections; |
1022 |
if ( length($string) > 35 ) { |
1023 |
my ( $chunk, $stringlength ) = ( 35, length($string) ); |
1024 |
for ( my $counter = 0 ; $counter < $stringlength ; $counter += $chunk ) |
1025 |
{ |
1026 |
push @sections, substr( $string, $counter, $chunk ); |
1027 |
} |
1028 |
foreach my $section (@sections) { |
1029 |
$colon_string .= $section . ":"; |
1030 |
} |
1031 |
chop $colon_string; |
1032 |
} |
1033 |
else { |
1034 |
$colon_string = $string; |
1035 |
} |
1036 |
return $colon_string; |
1037 |
} |
1038 |
|
1039 |
sub GetOrderItemInfo { |
1040 |
my $ordernumber = shift; |
1041 |
my $dbh = C4::Context->dbh; |
1042 |
my $sth; |
1043 |
my @rows; |
1044 |
my $homebranch; |
1045 |
my $callnumber; |
1046 |
my $itype; |
1047 |
my $ccode; |
1048 |
my $fund; |
1049 |
$sth = $dbh->prepare( |
1050 |
q|select items.homebranch, items.itemcallnumber, items.itype, items.location from items |
1051 |
inner join aqorders_items on aqorders_items.itemnumber=items.itemnumber |
1052 |
where aqorders_items.ordernumber=?| |
1053 |
); |
1054 |
$sth->execute($ordernumber); |
1055 |
|
1056 |
while ( @rows = $sth->fetchrow_array() ) { |
1057 |
$homebranch = $rows[0]; |
1058 |
$callnumber = $rows[1]; |
1059 |
$itype = $rows[2]; |
1060 |
$ccode = $rows[3]; |
1061 |
} |
1062 |
$sth = $dbh->prepare( |
1063 |
q|select aqbudgets.budget_code from aqbudgets inner join aqorders on |
1064 |
aqorders.budget_id=aqbudgets.budget_id where aqorders.ordernumber=?| |
1065 |
); |
1066 |
$sth->execute($ordernumber); |
1067 |
while ( @rows = $sth->fetchrow_array() ) { |
1068 |
$fund = $rows[0]; |
1069 |
} |
1070 |
return $homebranch, $callnumber, $itype, $ccode, $fund; |
1071 |
} |
1072 |
|
1073 |
sub CheckVendorFTPAccountExists { |
1074 |
my $booksellerid = shift; |
1075 |
my $dbh = C4::Context->dbh; |
1076 |
my $sth = $dbh->prepare( |
1077 |
q|select count(id) from vendor_edi_accounts where provider=?|); |
1078 |
$sth->execute($booksellerid); |
1079 |
while ( my @rows = $sth->fetchrow_array() ) { |
1080 |
if ( $rows[0] > 0 ) { |
1081 |
return 1; |
1082 |
} |
1083 |
} |
1084 |
return; |
1085 |
} |
1086 |
|
1087 |
1; |
1088 |
|
1089 |
__END__ |
1090 |
|
1091 |
=head1 AUTHOR |
1092 |
|
1093 |
Mark Gavillet |
1094 |
|
1095 |
=cut |