Bug 3050 - Add an option to upload scanned invoices #1/3
authorJacek Ablewicz <abl@biblos.pk.edu.pl>
Fri, 9 May 2014 09:30:28 +0000 (11:30 +0200)
committerGalen Charlton <gmc@esilibrary.com>
Sun, 25 May 2014 21:14:00 +0000 (21:14 +0000)
(part #1: new module w/ UT + script + template)

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) Apply patch[es]
2) Run installer/data/mysql/updatedatabase.pl
3) Enable system preference 'AcqEnableFiles' in acquisition
4) New option 'Manage invoice files' appears in the invoice
detail page
5) Upload/view/download/delete some files for some invoices
6) Try to delete invoice with files attached (files should
get deleted as well)
7) Try to merge 2+ invoices with files attached; after merge,
all files previously attached to individual invoices being
merged should be attached to resulting invoice (merge destination)
8) prove t/db_dependent/Koha_Misc_Files.t
9) Ensure there are no regressions of any kind in invoice detail
page (acqui/invoice.pl).

Signed-off-by: Paola Rossi <paola.rossi@cineca.it>
Signed-off-by: Jonathan Druart <jonathan.druart@biblibre.com>
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Koha/Misc/Files.pm [new file with mode: 0644]
acqui/invoice-files.pl [new file with mode: 0755]
koha-tmpl/intranet-tmpl/prog/en/modules/acqui/invoice-files.tt [new file with mode: 0644]
t/db_dependent/Koha_Misc_Files.t [new file with mode: 0755]

diff --git a/Koha/Misc/Files.pm b/Koha/Misc/Files.pm
new file mode 100644 (file)
index 0000000..1aaf04f
--- /dev/null
@@ -0,0 +1,266 @@
+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 <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+use vars qw($VERSION);
+$VERSION = '0.25';
+
+use C4::Context;
+use C4::Output;
+use C4::Dates;
+
+=head1 NAME
+
+Koha::Misc::Files - module for managing miscellaneous files associated
+with records from arbitrary tables
+
+=head1 SYNOPSIS
+
+use Koha::Misc::Files;
+
+my $mf = Koha::Misc::Files->new( tabletag => $tablename,
+    recordid => $recordnumber );
+
+=head1 FUNCTIONS
+
+=over
+
+=item new()
+
+my $mf = Koha::Misc::Files->new( tabletag => $tablename,
+    recordid => $recordnumber );
+
+Creates new Koha::Misc::Files object. Such object is essentially
+a pair: in typical usage scenario, 'tabletag' parameter will be
+a database table name, and 'recordid' an unique record ID number
+from this table. However, this method does accept an arbitrary
+string as 'tabletag', and an arbitrary integer as 'recordid'.
+
+Particular Koha::Misc::Files object can have one or more file records
+(actuall file contents + various file metadata) associated with it.
+
+In case of an error (wrong parameter format) it returns undef.
+
+=cut
+
+sub new {
+    my ( $class, %args ) = @_;
+
+    my $recid = $args{'recordid'};
+    my $tag   = $args{'tabletag'};
+    ( defined($tag) && $tag ne '' && defined($recid) && $recid =~ /^\d+$/ )
+      || return ();
+
+    my $self = bless( {}, $class );
+
+    $self->{'table_tag'} = $tag;
+    $self->{'record_id'} = '' . ( 0 + $recid );
+
+    return $self;
+}
+
+=item GetFilesInfo()
+
+my $files_descriptions = $mf->GetFilesInfo();
+
+This method returns a reference to an array of hashes
+containing files metadata (file_id, file_name, file_type,
+file_description, file_size, date_uploaded) for all file records
+associated with given $mf object, or an empty arrayref if there are
+no such records yet.
+
+In case of an error it returns undef.
+
+=cut
+
+sub GetFilesInfo {
+    my $self = shift;
+
+    my $dbh   = C4::Context->dbh;
+    my $query = '
+        SELECT
+            file_id,
+            file_name,
+            file_type,
+            file_description,
+            date_uploaded,
+            LENGTH(file_content) AS file_size
+        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()
+
+$mf->AddFile( name => $filename, type => $mimetype,
+    description => $description, content => $content );
+
+Adds a new file (we want to store for / associate with a given
+object) to the database. Parameters 'name' and 'content' are mandatory.
+Note: this method would (silently) fail if there is no 'name' given
+or if the 'content' provided is empty.
+
+=cut
+
+sub AddFile {
+    my ( $self, %args ) = @_;
+
+    my $name        = $args{'name'};
+    my $type        = $args{'type'} // '';
+    my $description = $args{'description'};
+    my $content     = $args{'content'};
+
+    return unless ( defined($name) && $name ne '' && defined($content) && $content ne '' );
+
+    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 $file = $mf->GetFile( id => $file_id );
+
+For an individual, specific file ID this method returns a hashref
+containing all metadata (file_id, table_tag, record_id, file_name,
+file_type, file_description, file_content, date_uploaded), plus
+an actuall contents of a file (in 'file_content'). In typical usage
+scenarios, for a given $mf object, specific file IDs have to be
+obtained first by GetFilesInfo() call.
+
+Returns undef in case when file ID specified as 'id' parameter was not
+found in the database.
+
+=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()
+
+$mf->DelFile( id => $file_id );
+
+Deletes specific, individual file record (file contents and metadata)
+from the database.
+
+=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()
+
+$mf->DelAllFiles();
+
+Deletes all file records associated with (stored for) a given $mf object.
+
+=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'} );
+}
+
+=item MergeFileRecIds()
+
+$mf->MergeFileRecIds(@ids_to_be_merged);
+
+This method re-associates all individuall file records associated with
+some "parent" records IDs (provided in @ids_to_be_merged) with the given
+single $mf object (which would be treated as a "parent" destination).
+
+This a helper method; typically it needs to be called only in cases when
+some "parent" records are being merged in the (external) 'tablename'
+table.
+
+=cut
+
+sub MergeFileRecIds {
+    my ( $self, @ids_to_merge ) = @_;
+
+    my $dst_recid = $self->{'record_id'};
+    @ids_to_merge = map { ( $dst_recid == $_ ) ? () : ($_); } @ids_to_merge;
+    @ids_to_merge > 0 || return ();
+
+    my $dbh   = C4::Context->dbh;
+    my $query = '
+        UPDATE misc_files SET record_id = ?
+        WHERE table_tag = ? AND record_id = ?
+    ';
+    my $sth = $dbh->prepare($query);
+
+    for my $src_recid (@ids_to_merge) {
+        $sth->execute( $dst_recid, $self->{'table_tag'}, $src_recid );
+    }
+}
+
+1;
+
+__END__
+
+=back
+
+=head1 SEE ALSO
+
+Koha::Borrower::Files
+
+=head1 AUTHOR
+
+Kyle M Hall E<lt>kyle.m.hall@gmail.comE<gt>,
+Jacek Ablewicz E<lt>ablewicz@gmail.comE<gt>
+
+=cut
diff --git a/acqui/invoice-files.pl b/acqui/invoice-files.pl
new file mode 100755 (executable)
index 0000000..635c427
--- /dev/null
@@ -0,0 +1,121 @@
+#!/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 <http://www.gnu.org/licenses>.
+
+=head1 NAME
+
+invoice-files.pl
+
+=head1 DESCRIPTION
+
+Manage files associated with invoice
+
+=cut
+
+use Modern::Perl;
+
+use CGI;
+use C4::Auth;
+use C4::Output;
+use C4::Acquisition;
+use Koha::Misc::Files;
+
+my $input = new CGI;
+my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
+    {
+        template_name   => 'acqui/invoice-files.tt',
+        query           => $input,
+        type            => 'intranet',
+        authnotrequired => 0,
+        flagsrequired   => { 'acquisition' => '*' },
+        debug           => 1,
+    }
+);
+
+my $invoiceid = $input->param('invoiceid') // '';
+my $op = $input->param('op') // '';
+my %errors;
+
+my $mf = Koha::Misc::Files->new( tabletag => 'aqinvoices', recordid => $invoiceid );
+defined($mf) || do { $op = 'none'; $errors{'invalid_parameter'} = 1; };
+
+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 =~ m|^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'},
+    );
+
+    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 );
+            unless (%errors) {
+                my $file_content = do { local $/; <$uploaded_file>; };
+                if ($mimetype =~ /^application\/(force-download|unknown)$/i && $filename =~ /\.pdf$/i) {
+                    $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 => (defined($mf)? $mf->GetFilesInfo(): undef),
+        errors => \%errors
+    );
+    output_html_with_http_headers $input, $cookie, $template->output;
+}
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 (file)
index 0000000..1121536
--- /dev/null
@@ -0,0 +1,100 @@
+[% USE KohaDates %]
+
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Acquisitions &rsaquo; Invoice &rsaquo; Files</title>
+<link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
+[% INCLUDE 'doc-head-close.inc' %]
+[% INCLUDE 'datatables.inc' %]
+<script type="text/javascript">
+//<![CDATA[
+    $(document).ready(function() {
+        $("#invoice_files_details_table").dataTable($.extend(true, {}, dataTablesDefaults, {
+            "aoColumnDefs": [
+                { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable": false },
+                { "aTargets": [ 3 ], "sType": "natural" }
+            ],
+            bInfo: false,
+            bPaginate: false,
+            bFilter: false,
+            sDom: "t"
+        }));
+    });
+//]]>
+</script>
+</head>
+<body>
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'acquisitions-search.inc' %]
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/acqui/acqui-home.pl">Acquisitions</a> &rsaquo; <a href="/cgi-bin/koha/acqui/invoices.pl">Invoices</a> &rsaquo; <a href="/cgi-bin/koha/acqui/invoice.pl?invoiceid=[% invoiceid %]">[% invoicenumber %]</a> &rsaquo; Files</div>
+
+<div id="doc3" class="yui-t2">
+
+<div id="bd">
+  <div id="yui-main">
+    <div class="yui-b">
+      <h2>Files for invoice: [% invoicenumber | html %]</h2>
+      <p><b>Vendor: </b><a href="/cgi-bin/koha/acqui/supplier.pl?booksellerid=[% booksellerid %]">[% suppliername %]</a></p>
+      <br />
+      [% IF errors %]
+        <div class="dialog alert">
+          [% 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 %]
+          [% IF errors.invalid_parameter %]Invalid or missing script parameter.[% END %]
+        </div>
+      [% END %]
+      [% IF files %]
+          <table id="invoice_files_details_table">
+              <thead>
+                  <tr>
+                      <th>Name</th>
+                      <th>Type</th>
+                      <th>Description</th>
+                      <th>Uploaded</th>
+                      <th>Bytes</th>
+                      <th>&nbsp;</th>
+                      <th>&nbsp;</th>
+                  </tr>
+              </thead>
+              <tbody>
+                [% FOREACH f IN files %]
+                  <tr>
+                      <td><a href="?invoiceid=[% invoiceid %]&amp;op=download&amp;view=1&amp;file_id=[% f.file_id %]">[% f.file_name | html %]</a></td>
+                      <td>[% f.file_type | html %]</td>
+                      <td>[% f.file_description | html %]</td>
+                      <td><!-- [% f.date_uploaded %] -->[% f.date_uploaded | $KohaDates %]</td>
+                      <td>[% f.file_size %]</td>
+                      <td><a href="?invoiceid=[% invoiceid %]&amp;op=delete&amp;file_id=[% f.file_id %]">Delete</a></td>
+                      <td><a href="?invoiceid=[% invoiceid %]&amp;op=download&amp;file_id=[% f.file_id %]">Download</a></td>
+                  </tr>
+                [% END %]
+              </tbody>
+          </table>
+      [% ELSE %]
+          <div class="dialog message">
+              <p>This invoice has no files attached.</p>
+          </div>
+      [% END %]
+      [% IF invoiceid %]
+          <br />
+          <form method="post" action="/cgi-bin/koha/acqui/invoice-files.pl" enctype="multipart/form-data">
+              <fieldset class="rows">
+                  <legend>Upload New File</legend>
+                  <ol>
+                      <li><input type="hidden" name="op" value="upload" />
+                      <input type="hidden" name="invoiceid" value="[% invoiceid %]" />
+                      <label for="description">Description:</label>
+                      <input name="description" id="description" type="text" /></li>
+                      <li><label for="uploadfile">File:</label><input name="uploadfile" type="file" id="uploadfile" /></li>
+                  </ol>
+                  <fieldset class="action"><input name="upload" type="submit" id="upload" value="Upload File" /></fieldset>
+              </fieldset>
+          </form>
+      [% END %]
+    </div>
+  </div>
+  <div class="yui-b">
+    [% INCLUDE 'acquisitions-menu.inc' %]
+  </div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/t/db_dependent/Koha_Misc_Files.t b/t/db_dependent/Koha_Misc_Files.t
new file mode 100755 (executable)
index 0000000..d88da5c
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+
+# Unit tests for Koha::Misc::Files
+# Author: Jacek Ablewicz, abl@biblos.pk.edu.pl
+
+use Modern::Perl;
+use C4::Context;
+use Test::More tests => 27;
+
+BEGIN {
+    use_ok('Koha::Misc::Files');
+}
+
+my $dbh = C4::Context->dbh;
+$dbh->{AutoCommit} = 0;
+$dbh->{RaiseError} = 1;
+
+## new() parameter handling check
+is(Koha::Misc::Files->new(recordid => 12), undef, "new() param check test/1");
+is(Koha::Misc::Files->new(recordid => 'aa123', tabletag => 'ttag_a'), undef, "new() param check test/2");
+
+## create some test objects with arbitrary (tabletag, recordid) pairs
+my $mf_a_123 = Koha::Misc::Files->new(recordid => '123', tabletag => 'tst_table_a');
+my $mf_a_124 = Koha::Misc::Files->new(recordid => '124', tabletag => 'tst_table_a');
+my $mf_b_221 = Koha::Misc::Files->new(recordid => '221', tabletag => 'tst_table_b');
+is(ref($mf_a_123), "Koha::Misc::Files", "new() returned object type");
+
+## GetFilesInfo() initial tests (dummy AddFile() / parameter handling checks)
+is(ref($mf_a_123->GetFilesInfo()), 'ARRAY', "GetFilesInfo() return type");
+is(scalar @{$mf_a_123->GetFilesInfo()}, 0, "GetFilesInfo() empty/non-empty result/1");
+$mf_a_123->AddFile(name => '', type => 'text/plain', content => "aaabbcc");
+is(scalar @{$mf_a_123->GetFilesInfo()}, 0, "GetFilesInfo() empty/non-empty result/2");
+
+## AddFile(); add 5 sample file records for 3 test objects
+$mf_a_123->AddFile(name => 'File_name_1.txt', type => 'text/plain',
+  content => "file contents\n1111\n", description => "File #1 sample description");
+$mf_a_123->AddFile(name => 'File_name_2.txt', type => 'text/plain',
+  content => "file contents\n2222\n", description => "File #2 sample description");
+$mf_a_124->AddFile(name => 'File_name_3.txt', content => "file contents\n3333\n", type => 'text/whatever');
+$mf_a_124->AddFile(name => 'File_name_4.txt', content => "file contents\n4444\n");
+$mf_b_221->AddFile(name => 'File_name_5.txt', content => "file contents\n5555\n");
+
+## check GetFilesInfo() results for added files
+my $files_a_123_infos = $mf_a_123->GetFilesInfo();
+is(scalar @$files_a_123_infos, 2, "GetFilesInfo() result count/1");
+is(scalar @{$mf_b_221->GetFilesInfo()}, 1, "GetFilesInfo() result count/2");
+is(ref($files_a_123_infos->[0]), 'HASH', "GetFilesInfo() item file result type");
+is($files_a_123_infos->[0]->{file_name}, 'File_name_1.txt', "GetFilesInfo() result check/1");
+is($files_a_123_infos->[1]->{file_name}, 'File_name_2.txt', "GetFilesInfo() result check/2");
+is($files_a_123_infos->[1]->{file_type}, 'text/plain', "GetFilesInfo() result check/3");
+is($files_a_123_infos->[1]->{file_size}, 19, "GetFilesInfo() result check/4");
+is($files_a_123_infos->[1]->{file_description}, 'File #2 sample description', "GetFilesInfo() result check/5");
+
+## GetFile() result checks
+is($mf_a_123->GetFile(), undef, "GetFile() result check/1");
+is($mf_a_123->GetFile(id => 0), undef, "GetFile() result check/2");
+
+my $a123_file_1 = $mf_a_123->GetFile(id => $files_a_123_infos->[0]->{file_id});
+is(ref($a123_file_1), 'HASH', "GetFile() result check/3");
+is($a123_file_1->{file_id}, $files_a_123_infos->[0]->{file_id}, "GetFile() result check/4");
+is($a123_file_1->{file_content}, "file contents\n1111\n", "GetFile() result check/5");
+
+## MergeFileRecIds() tests
+$mf_a_123->MergeFileRecIds(123,221);
+$files_a_123_infos = $mf_a_123->GetFilesInfo();
+is(scalar @$files_a_123_infos, 2, "GetFilesInfo() result count after dummy MergeFileRecIds()");
+$mf_a_123->MergeFileRecIds(124);
+$files_a_123_infos = $mf_a_123->GetFilesInfo();
+is(scalar @$files_a_123_infos, 4, "GetFilesInfo() result count after MergeFileRecIds()/1");
+is(scalar @{$mf_a_124->GetFilesInfo()}, 0, "GetFilesInfo() result count after MergeFileRecIds()/2");
+is($files_a_123_infos->[-1]->{file_name}, 'File_name_4.txt', "GetFilesInfo() result check after MergeFileRecIds()");
+
+## DelFile() test
+$mf_a_123->DelFile(id => $files_a_123_infos->[-1]->{file_id});
+$files_a_123_infos = $mf_a_123->GetFilesInfo();
+is(scalar @$files_a_123_infos, 3, "GetFilesInfo() result count after DelFile()");
+
+## DelAllFiles() tests
+$mf_a_123->DelAllFiles();
+$files_a_123_infos = $mf_a_123->GetFilesInfo();
+is(scalar @$files_a_123_infos, 0, "GetFilesInfo() result count after DelAllFiles()/1");
+$mf_b_221->DelAllFiles();
+is(scalar @{$mf_b_221->GetFilesInfo()}, 0, "GetFilesInfo() result count after DelAllFiles()/2");
+
+$dbh->rollback;
+
+1;