1 package Koha
::Edifact
::Transport
;
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>.
25 use English
qw{ -no_match_vars
};
27 use Net
::SFTP
::Foreign
;
30 use File
::Basename
qw( fileparse );
33 use Encode
qw( from_to );
36 my ( $class, $account_id ) = @_;
37 my $database = Koha
::Database
->new();
38 my $schema = $database->schema();
39 my $acct = $schema->resultset('VendorEdiAccount')->find($account_id);
43 working_dir
=> C4
::Context
::temporary_directory
, #temporary work directory
44 transfer_date
=> dt_from_string
(),
51 sub working_directory
{
52 my ( $self, $new_value ) = @_;
54 $self->{working_dir
} = $new_value;
56 return $self->{working_dir
};
59 sub download_messages
{
60 my ( $self, $message_type ) = @_;
61 $self->{message_type
} = $message_type;
65 if ( $self->{account
}->transport eq 'SFTP' ) {
66 @retrieved_files = $self->sftp_download();
68 elsif ( $self->{account
}->transport eq 'FILE' ) {
69 @retrieved_files = $self->file_download();
72 @retrieved_files = $self->ftp_download();
74 return @retrieved_files;
78 my ( $self, @messages ) = @_;
80 if ( $self->{account
}->transport eq 'SFTP' ) {
81 $self->sftp_upload(@messages);
83 elsif ( $self->{account
}->transport eq 'FILE' ) {
84 $self->file_upload(@messages);
87 $self->ftp_upload(@messages);
97 my $file_ext = _get_file_ext
( $self->{message_type
} );
99 my $dir = $self->{account
}->download_directory; # makes code more readable
100 # C = ready to retrieve E = Edifact
101 my $msg_hash = $self->message_hash();
102 if ( opendir my $dh, $dir ) {
103 my @file_list = readdir $dh;
105 foreach my $filename (@file_list) {
107 if ( $filename =~ m/[.]$file_ext$/ ) {
108 if ( copy
( "$dir/$filename", $self->{working_dir
} ) ) {
111 carp
"copy of $filename failed";
114 push @downloaded_files, $filename;
115 my $processed_name = $filename;
116 substr $processed_name, -3, 1, 'E';
117 move
( "$dir/$filename", "$dir/$processed_name" );
120 $self->ingest( $msg_hash, @downloaded_files );
123 carp
"Cannot open $dir";
126 return @downloaded_files;
132 my $file_ext = _get_file_ext
( $self->{message_type
} );
134 # C = ready to retrieve E = Edifact
135 my $msg_hash = $self->message_hash();
136 my @downloaded_files;
137 my $sftp = Net
::SFTP
::Foreign
->new(
138 host
=> $self->{account
}->host,
139 user
=> $self->{account
}->username,
140 password
=> $self->{account
}->password,
143 if ( $sftp->error ) {
144 return $self->_abort_download( undef,
145 'Unable to connect to remote host: ' . $sftp->error );
147 $sftp->setcwd( $self->{account
}->download_directory )
148 or return $self->_abort_download( $sftp,
149 "Cannot change remote dir : $sftp->error" );
150 my $file_list = $sftp->ls()
151 or return $self->_abort_download( $sftp,
152 "cannot get file list from server: $sftp->error" );
153 foreach my $file ( @
{$file_list} ) {
154 my $filename = $file->{filename
};
156 if ( $filename =~ m/[.]$file_ext$/ ) {
157 $sftp->get( $filename, "$self->{working_dir}/$filename" );
158 if ( $sftp->error ) {
159 $self->_abort_download( $sftp,
160 "Error retrieving $filename: $sftp->error" );
163 push @downloaded_files, $filename;
164 my $processed_name = $filename;
165 substr $processed_name, -3, 1, 'E';
167 #$sftp->atomic_rename( $filename, $processed_name );
168 my $ret = $sftp->rename( $filename, $processed_name );
170 $self->_abort_download( $sftp,
171 "Error renaming $filename: $sftp->error" );
178 $self->ingest( $msg_hash, @downloaded_files );
180 return @downloaded_files;
184 my ( $self, $msg_hash, @downloaded_files ) = @_;
185 foreach my $f (@downloaded_files) {
187 # Check file has not been downloaded already
188 my $existing_file = $self->{schema
}->resultset('EdifactMessage')
189 ->find( { filename
=> $f, } );
190 if ($existing_file) {
191 carp
"skipping ingest of $f : filename exists";
195 $msg_hash->{filename
} = $f;
197 read_file
( "$self->{working_dir}/$f", binmode => ':raw' );
198 if ( !defined $file_content ) {
199 carp
"Unable to read download file $f";
202 from_to
( $file_content, 'iso-8859-1', 'utf8' );
203 $msg_hash->{raw_msg
} = $file_content;
204 $self->{schema
}->resultset('EdifactMessage')->create($msg_hash);
212 my $file_ext = _get_file_ext
( $self->{message_type
} );
214 # C = ready to retrieve E = Edifact
216 my $msg_hash = $self->message_hash();
217 my @downloaded_files;
218 my $ftp = Net
::FTP
->new(
219 $self->{account
}->host,
223 or return $self->_abort_download( undef,
224 "Cannot connect to $self->{account}->host: $EVAL_ERROR" );
225 $ftp->login( $self->{account
}->username, $self->{account
}->password )
226 or return $self->_abort_download( $ftp, "Cannot login: $ftp->message()" );
227 $ftp->cwd( $self->{account
}->download_directory )
228 or return $self->_abort_download( $ftp,
229 "Cannot change remote dir : $ftp->message()" );
230 my $file_list = $ftp->ls()
232 return $self->_abort_download( $ftp, 'cannot get file list from server' );
234 foreach my $filename ( @
{$file_list} ) {
236 if ( $filename =~ m/[.]$file_ext$/ ) {
238 if ( !$ftp->get( $filename, "$self->{working_dir}/$filename" ) ) {
239 $self->_abort_download( $ftp,
240 "Error retrieving $filename: $ftp->message" );
244 push @downloaded_files, $filename;
245 my $processed_name = $filename;
246 substr $processed_name, -3, 1, 'E';
247 $ftp->rename( $filename, $processed_name );
252 $self->ingest( $msg_hash, @downloaded_files );
254 return @downloaded_files;
258 my ( $self, @messages ) = @_;
259 my $ftp = Net
::FTP
->new(
260 $self->{account
}->host,
264 or return $self->_abort_download( undef,
265 "Cannot connect to $self->{account}->host: $EVAL_ERROR" );
266 $ftp->login( $self->{account
}->username, $self->{account
}->password )
267 or return $self->_abort_download( $ftp, "Cannot login: $ftp->message()" );
268 $ftp->cwd( $self->{account
}->upload_directory )
269 or return $self->_abort_download( $ftp,
270 "Cannot change remote dir : $ftp->message()" );
271 foreach my $m (@messages) {
272 my $content = $m->raw_msg;
274 open my $fh, '<', \
$content;
275 if ( $ftp->put( $fh, $m->filename ) ) {
277 $m->transfer_date( $self->{transfer_date
} );
293 my ( $self, @messages ) = @_;
294 my $sftp = Net
::SFTP
::Foreign
->new(
295 host
=> $self->{account
}->host,
296 user
=> $self->{account
}->username,
297 password
=> $self->{account
}->password,
300 $sftp->die_on_error("Cannot ssh to $self->{account}->host");
301 $sftp->setcwd( $self->{account
}->upload_directory )
302 or return $self->_abort_download( $sftp,
303 "Cannot change remote dir : $sftp->error" );
304 foreach my $m (@messages) {
305 my $content = $m->raw_msg;
307 open my $fh, '<', \
$content;
308 if ( $sftp->put( $fh, $m->filename ) ) {
310 $m->transfer_date( $self->{transfer_date
} );
321 # sftp will be closed on object destructor
326 my ( $self, @messages ) = @_;
327 my $dir = $self->{account
}->upload_directory;
329 foreach my $m (@messages) {
330 my $content = $m->raw_msg;
332 my $filename = $m->filename;
333 my $new_filename = "$dir/$filename";
334 if ( open my $fh, '>', $new_filename ) {
335 print {$fh} $content;
337 $m->transfer_date( $self->{transfer_date
} );
342 carp
"Could not transfer $m->filename : $ERRNO";
349 carp
"Upload directory $dir does not exist";
354 sub _abort_download
{
355 my ( $self, $handle, $log_message ) = @_;
357 my $a = $self->{account
}->description;
362 $log_message .= ": $a";
365 #returns undef i.e. an empty array
373 # 1st char Status C = Ready For pickup A = Completed E = Extracted
374 # 2nd Char Standard E = Edifact
375 # 3rd Char Type of message
382 if ( exists $file_types{$type} ) {
383 return $file_types{$type};
385 return 'XXXX'; # non matching type
391 message_type
=> $self->{message_type
},
392 vendor_id
=> $self->{account
}->vendor_id,
393 edi_acct
=> $self->{account
}->id,
396 transfer_date
=> $self->{transfer_date
}->ymd(),
407 Koha::Edifact::Transport
411 my $download = Koha::Edifact::Transport->new( $vendor_edi_account_id );
412 $downlowd->download_messages('QUOTE');
417 Module that handles Edifact download and upload transport
418 currently can use sftp or ftp
419 Or FILE to access a local directory (useful for testing)
426 Creates an object of Edifact::Transport requires to be passed the id
427 identifying the relevant edi vendor account
429 =head2 working_directory
431 getter and setter for the working_directory attribute
433 =head2 download_messages
435 called with the message type to download will perform the download
436 using the appropriate transport method
438 =head2 upload_messages
440 passed an array of messages will upload them to the supplier site
444 called by download_messages to perform the download using SFTP
448 loads downloaded files into the database
452 called by download_messages to perform the download using FTP
456 called by upload_messages to perform the upload using ftp
460 called by upload_messages to perform the upload using sftp
462 =head2 _abort_download
464 internal routine to halt operation on error and supply a stacktrace
468 internal method returning standard suffix for file names
469 according to message type
471 =head2 set_transport_direct
473 sets the direct ingest flag so that the object reads files from
474 the local file system useful in debugging
478 Colin Campbell <colin.campbell@ptfs-europe.com>
483 Copyright 2014,2015 PTFS-Europe Ltd
484 This program is free software, You may redistribute it under
485 under the terms of the GNU General Public License