From bcd8dac9f5b5f6026c67d32c920f146774d26bc6 Mon Sep 17 00:00:00 2001 From: Jacek Ablewicz Date: Fri, 14 Feb 2014 10:58:36 +0100 Subject: [PATCH] [SIGNED-OFF] Bug 3050 - Add an option to upload scanned invoices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New feature, adds an ability to attach arbitrary files to acquisition records (currently: to the invoices - but it can be extended to baskets, basketgroups, budgets etc.). Note: this code is (heavily) based on "Bug 8130 - attach PDF files to a patron record" by Kale M Hall, main difference being that new table (misc_files) and new module (Koha/Misc/Files.pm) are intended to be a little more generic solution - they allow to store and manage files associated with great many kinds of records, from arbitrary tables. Test plan: 1) Enable system preference 'AcqEnableFiles' in acquisition 2) New option 'Manage invoice files' appears in the invoice detail page 3) Upload/view/download/delete some files for the invoice 4) Try to delete invoice with files attached (files should get deleted as well) 5) Ensure there are no regressions of any kind in invoice detail page (acqui/invoice.pl). Tested with several file types. Works as expected (and is a great feature!) Signed-off-by: Marc VĂ©ron --- C4/Auth.pm | 1 + Koha/Misc/Files.pm | 174 ++++++++++++++++++++ acqui/invoice-files.pl | 132 +++++++++++++++ acqui/invoice.pl | 11 ++ installer/data/mysql/kohastructure.sql | 18 ++ installer/data/mysql/updatedatabase.pl | 22 +++ .../prog/en/modules/acqui/invoice-files.tt | 86 ++++++++++ .../intranet-tmpl/prog/en/modules/acqui/invoice.tt | 25 +++ .../en/modules/admin/preferences/acquisitions.pref | 6 + 9 files changed, 475 insertions(+) create mode 100644 Koha/Misc/Files.pm create mode 100755 acqui/invoice-files.pl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice-files.tt diff --git a/C4/Auth.pm b/C4/Auth.pm index 44edf67..2708d43 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -358,6 +358,7 @@ sub get_template_and_user { EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'), UseKohaPlugins => C4::Context->preference('UseKohaPlugins'), UseCourseReserves => C4::Context->preference("UseCourseReserves"), + AcqEnableFiles => C4::Context->preference('AcqEnableFiles'), ); } else { diff --git a/Koha/Misc/Files.pm b/Koha/Misc/Files.pm new file mode 100644 index 0000000..b8ed3eb --- /dev/null +++ b/Koha/Misc/Files.pm @@ -0,0 +1,174 @@ +package Koha::Misc::Files; + +# This file is part of Koha. +# +# Copyright 2012 Kyle M Hall +# Copyright 2014 Jacek Ablewicz +# Based on Koha/Borrower/Files.pm by Kyle M Hall +# +# 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 3 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, see . + +use Modern::Perl; +use strict; ## not needed if Modern::Perl, but perlcritic complains.. +use vars qw($VERSION); +$VERSION = '0.20'; + +use C4::Context; +use C4::Output; +use C4::Dates; +use C4::Debug; + +=head1 NAME + +Koha::Misc::Files - module for managing miscellaneous files +associated with records from arbitrary tables + +=cut + +sub new { + my ( $class, %args ) = @_; + + ( defined( $args{'tabletag'} ) && defined( $args{'recordid'} ) ) + || return (); + my $self = bless( {}, $class ); + + $self->{'table_tag'} = $args{'tabletag'}; + $self->{'record_id'} = $args{'recordid'}; + + return $self; +} + +=item GetFilesInfo() + + my $mf = Koha::Misc::Files->new( tabletag => $tablename, + recordid => $recordnumber); + my $files_hashref = $mf->GetFilesInfo + +=cut + +sub GetFilesInfo { + my $self = shift; + + my $dbh = C4::Context->dbh; + my $query = " + SELECT + file_id, + file_name, + file_type, + file_description, + date_uploaded + FROM misc_files + WHERE table_tag = ? AND record_id = ? + ORDER BY file_name, date_uploaded + "; + my $sth = $dbh->prepare($query); + $sth->execute( $self->{'table_tag'}, $self->{'record_id'} ); + return $sth->fetchall_arrayref( {} ); +} + +=item AddFile() + my $mf = Koha::Misc::Files->new( tabletag => $tablename, + recordid => $recordnumber); + $mf->AddFile( name => $filename, type => $mimetype, description => $description, content => $content ); +=cut + +sub AddFile { + my ( $self, %args ) = @_; + + my $name = $args{'name'}; + my $type = $args{'type'}; + my $description = $args{'description'}; + my $content = $args{'content'}; + + return unless ( $name && $content ); + + my $dbh = C4::Context->dbh; + my $query = " + INSERT INTO misc_files ( table_tag, record_id, file_name, file_type, file_description, file_content ) + VALUES ( ?,?,?,?,?,? ) + "; + my $sth = $dbh->prepare($query); + $sth->execute( $self->{'table_tag'}, $self->{'record_id'}, $name, $type, + $description, $content ); +} + +=item GetFile() + my $mf = Koha::Misc::Files->new( tabletag => $tablename, + recordid => $recordnumber); + ... + my $file = $mf->GetFile( id => $file_id ); +=cut + +sub GetFile { + my ( $self, %args ) = @_; + + my $file_id = $args{'id'}; + + my $dbh = C4::Context->dbh; + my $query = " + SELECT * FROM misc_files WHERE file_id = ? AND table_tag = ? AND record_id = ? + "; + my $sth = $dbh->prepare($query); + $sth->execute( $file_id, $self->{'table_tag'}, $self->{'record_id'} ); + return $sth->fetchrow_hashref(); +} + +=item DelFile() + my $mf = Koha::Misc::Files->new( tabletag => $tablename, + recordid => $recordnumber); + ... + $mf->DelFile( id => $file_id ); +=cut + +sub DelFile { + my ( $self, %args ) = @_; + + my $file_id = $args{'id'}; + + my $dbh = C4::Context->dbh; + my $query = " + DELETE FROM misc_files WHERE file_id = ? AND table_tag = ? AND record_id = ? + "; + my $sth = $dbh->prepare($query); + $sth->execute( $file_id, $self->{'table_tag'}, $self->{'record_id'} ); +} + +=item DelAllFiles() + my $mf = Koha::Misc::Files->new( tabletag => $tablename, + recordid => $recordnumber); + $mf->DelAllFiles; +=cut + +sub DelAllFiles { + my ($self) = @_; + + my $dbh = C4::Context->dbh; + my $query = " + DELETE FROM misc_files WHERE table_tag = ? AND record_id = ? + "; + my $sth = $dbh->prepare($query); + $sth->execute( $self->{'table_tag'}, $self->{'record_id'} ); +} + +1; +__END__ + +=back + +=head1 AUTHOR + +Kyle M Hall +Jacek Ablewicz + +=cut diff --git a/acqui/invoice-files.pl b/acqui/invoice-files.pl new file mode 100755 index 0000000..a2f7926 --- /dev/null +++ b/acqui/invoice-files.pl @@ -0,0 +1,132 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright 2014 Jacek Ablewicz +# +# 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 3 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, see . + +=head1 NAME + +invoice-files.pl + +=head1 DESCRIPTION + +Manage files associated with invoice + +=cut + +use strict; +use warnings; + +use CGI; +use C4::Auth; +use C4::Output; +use C4::Acquisition; +use C4::Debug; +use Koha::DateUtils; +use Koha::Misc::Files; + +my $input = new CGI; +my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user( + { + template_name => 'acqui/invoice-files.tmpl', + query => $input, + type => 'intranet', + authnotrequired => 0, + flagsrequired => { 'acquisition' => '*' }, + debug => 1, + } +); + +my $invoiceid = $input->param('invoiceid'); +my $op = $input->param('op') // ''; + +$template->param( 'invoice_files' => 1 ); +my $mf = Koha::Misc::Files->new( tabletag => 'aqinvoices', recordid => $invoiceid ); + +if ( $op eq 'download' ) { + my $file_id = $input->param('file_id'); + my $file = $mf->GetFile( id => $file_id ); + + my $fname = $file->{'file_name'}; + my $ftype = $file->{'file_type'}; + if ($input->param('view') && ($ftype =~ /^image\//i || $fname =~ /\.pdf/i)) { + $fname =~ /\.pdf/i && do { $ftype='application/pdf'; }; + print $input->header( + -type => $ftype, + -charset => 'utf-8', + ); + } else { + print $input->header( + -type => $file->{'file_type'}, + -charset => 'utf-8', + -attachment => $file->{'file_name'} + ); + } + print $file->{'file_content'}; +} +else { + my $details = GetInvoiceDetails($invoiceid); + $template->param( + invoiceid => $details->{'invoiceid'}, + invoicenumber => $details->{'invoicenumber'}, + suppliername => $details->{'suppliername'}, + booksellerid => $details->{'booksellerid'}, + datereceived => $details->{'datereceived'}, + ); + my %errors; + + if ( $op eq 'upload' ) { + my $uploaded_file = $input->upload('uploadfile'); + + if ($uploaded_file) { + my $filename = $input->param('uploadfile'); + my $mimetype = $input->uploadInfo($filename)->{'Content-Type'}; + + $errors{'empty_upload'} = 1 if ( -z $uploaded_file ); + + if (%errors) { + $template->param( errors => %errors ); + } + else { + my $file_content; + while (<$uploaded_file>) { + $file_content .= $_; + } + if ($mimetype =~ /^application\/(force-download|unknown)$/i && $filename =~ /\.pdf$/) { + $mimetype = 'application/pdf'; + } + $mf->AddFile( + name => $filename, + type => $mimetype, + content => $file_content, + description => $input->param('description'), + ); + } + } + else { + $errors{'no_file'} = 1; + } + } elsif ( $op eq 'delete' ) { + $mf->DelFile( id => $input->param('file_id') ); + } + + $template->param( + files => $mf->GetFilesInfo(), + errors => \%errors + ); + + output_html_with_http_headers $input, $cookie, $template->output; +} diff --git a/acqui/invoice.pl b/acqui/invoice.pl index 5971ed1..44a2f66 100755 --- a/acqui/invoice.pl +++ b/acqui/invoice.pl @@ -35,6 +35,7 @@ use C4::Output; use C4::Acquisition; use C4::Bookseller qw/GetBookSellerFromId/; use C4::Budgets; +use Koha::Misc::Files; my $input = new CGI; my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user( @@ -91,6 +92,9 @@ elsif ( $op && $op eq 'mod' ) { } elsif ( $op && $op eq 'delete' ) { DelInvoice($invoiceid); + C4::Context->preference('AcqEnableFiles') + && $invoiceid && Koha::Misc::Files->new( + tabletag => 'aqinvoices', recordid => $invoiceid )->DelAllFiles(); my $referer = $input->param('referer') || 'invoices.pl'; if ($referer) { print $input->redirect($referer); @@ -209,6 +213,13 @@ $template->param( budgets_loop => \@budgets_loop, ); +{ + C4::Context->preference('AcqEnableFiles') || last; + my $mf = Koha::Misc::Files->new( + tabletag => 'aqinvoices', recordid => $invoiceid ); + defined( $mf ) && $template->param( files => $mf->GetFilesInfo() ); +} + sub get_infos { my $order = shift; my $bookseller = shift; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 363217b..11ae229 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -3393,6 +3393,24 @@ CREATE TABLE IF NOT EXISTS marc_modification_template_actions ( CONSTRAINT `mmta_ibfk_1` FOREIGN KEY (`template_id`) REFERENCES `marc_modification_templates` (`template_id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- Table structure for table `misc_files` +-- + +CREATE TABLE IF NOT EXISTS `misc_files` ( -- miscellaneous files attached to records from various tables + `file_id` int(11) NOT NULL AUTO_INCREMENT, -- unique id for the file record + `table_tag` varchar(255) NOT NULL, -- usually table name, or arbitrary unique tag + `record_id` int(11) NOT NULL, -- record id from the table this file is associated to + `file_name` varchar(255) NOT NULL, -- file name + `file_type` varchar(255) NOT NULL, -- MIME type of the file + `file_description` varchar(255) DEFAULT NULL, -- description given to the file + `file_content` longblob NOT NULL, -- file content + `date_uploaded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- date and time the file was added + PRIMARY KEY (`file_id`), + KEY `table_tag` (`table_tag`), + KEY `record_id` (`record_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index f043d0b..722fb63 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -7953,6 +7953,28 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { SetVersion($DBversion); } +$DBversion = "3.15.00.XXX"; +if (CheckVersion($DBversion)) { + $dbh->do("INSERT INTO systempreferences (variable,value,explanation,type) VALUES('AcqEnableFiles','0','If enabled, allows librarians to upload and attach arbitrary files to invoice records.','YesNo')"); + $dbh->do(" +CREATE TABLE IF NOT EXISTS `misc_files` ( + `file_id` int(11) NOT NULL AUTO_INCREMENT, + `table_tag` varchar(255) NOT NULL, + `record_id` int(11) NOT NULL, + `file_name` varchar(255) NOT NULL, + `file_type` varchar(255) NOT NULL, + `file_description` varchar(255) DEFAULT NULL, + `file_content` longblob NOT NULL, -- file content + `date_uploaded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`file_id`), + KEY `table_tag` (`table_tag`), + KEY `record_id` (`record_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + "); + print "Upgrade to $DBversion done (Bug 3050 - Add an option to upload scanned invoices)\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice-files.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice-files.tt new file mode 100644 index 0000000..098e175 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice-files.tt @@ -0,0 +1,86 @@ +[% USE KohaDates %] + +[% INCLUDE 'doc-head-open.inc' %] +Koha › Acquisitions › Invoice › Files + +[% INCLUDE 'doc-head-close.inc' %] + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'acquisitions-search.inc' %] + + + +
+ +
+
+
+

Files for invoice: [% invoicenumber %]

+ +

Vendor: [% suppliername %]

+ + [% IF errors %] +
+ [% IF errors.empty_upload %]The file you are attempting to upload has no contents.[% END %] + [% IF errors.no_file %]You did not select a file to upload.[% END %] +
+ [% END %] + + [% IF ( files ) %] + + + + + + + + + + + + + + [% FOREACH f IN files %] + + + + + + + + + [% END %] + +
NameTypeDescriptionUploaded  
[% f.file_name %][% f.file_type %][% f.file_description %][% f.date_uploaded | $KohaDates %]DeleteDownload
+ [% ELSE %] +
+

This invoice has no files attached.

+
+ [% END %] + +
+
+ Upload New File +
    +
  1. + + + + +
  2. + +
  3. + +
+
+
+
+ +
+
+
+ [% INCLUDE 'acquisitions-menu.inc' %] +
+
+[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt index 266aa65..607f66b 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice.tt @@ -89,6 +89,7 @@

Go to receipt page + [% IF ( AcqEnableFiles ) %]| Manage invoice files[% END %]

Invoice details

[% IF orders_loop.size %] @@ -169,6 +170,30 @@ [% ELSE %]

No orders yet

[% END %] + [% IF ( AcqEnableFiles && files ) %] +
+

Files attached to invoice

+ + + + + + + + + + + [% FOREACH f IN files %] + + + + + + + [% END %] + +
NameTypeDescriptionUploaded
[% f.file_name %][% f.file_type %][% f.file_description %][% f.date_uploaded | $KohaDates %]
+ [% END %]
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref index 60ba8be..0c811ab 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/acquisitions.pref @@ -45,6 +45,12 @@ Acquisitions: - - Upon receiving items, update their subfields if they were created when placing an order (e.g. o=5|a="foo bar"). - pref: AcqItemSetSubfieldsWhenReceived + - + - pref: AcqEnableFiles + choices: + yes: Do + no: "Don't" + - enable the ability to upload and attach arbitrary files to invoices. Printing: - -- 1.7.10.4