Dateimanagement: Controller zum Laden und Generierung der Dateien
authorMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Wed, 25 Jan 2017 14:52:27 +0000 (15:52 +0100)
committerMartin Helmling martin.helmling@octosoft.eu <martin.helmling@octosoft.eu>
Wed, 1 Feb 2017 07:51:35 +0000 (08:51 +0100)
sowie die dazugehörenden Templates

SL/Controller/File.pm [new file with mode: 0644]
SL/Controller/Helper/ThumbnailCreator.pm [new file with mode: 0644]
SL/Form.pm
SL/Helper/File.pm [new file with mode: 0644]
js/kivi.File.js [new file with mode: 0644]
locale/de/all
sql/Pg-upgrade2-auth/other_file_sources.sql [new file with mode: 0644]
templates/webpages/file/import_dialog.html [new file with mode: 0644]
templates/webpages/file/list.html [new file with mode: 0644]
templates/webpages/file/upload_dialog.html [new file with mode: 0644]
templates/webpages/part/form.html

diff --git a/SL/Controller/File.pm b/SL/Controller/File.pm
new file mode 100644 (file)
index 0000000..05884dd
--- /dev/null
@@ -0,0 +1,770 @@
+package SL::Controller::File;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use List::Util qw(first max);
+
+use utf8;
+use Encode qw(decode);
+use URI::Escape;
+use Cwd;
+use DateTime;
+use File::stat;
+use File::Spec::Unix;
+use File::Spec::Win32;
+use File::MimeInfo::Magic;
+use SL::DB::Helper::Mappings;
+use SL::DB::Order;
+use SL::DB::DeliveryOrder;
+use SL::DB::Invoice;
+
+use SL::DB::PurchaseInvoice;
+use SL::DB::Part;
+use SL::DB::GLTransaction;
+use SL::DB::Draft;
+use SL::DB::History;
+use SL::JSON;
+use SL::Helper::CreatePDF qw(:all);
+use SL::Locale::String;
+use SL::SessionFile;
+use SL::File;
+use SL::Controller::Helper::ThumbnailCreator qw(file_probe_image_type);
+
+use constant DO_DELETE    =>   0;
+use constant DO_UNIMPORT  =>   1;
+
+
+use Rose::Object::MakeMethods::Generic
+(
+    'scalar --get_set_init' => [ qw() ],
+    'scalar' => [ qw(object object_type object_model object_id object_right file_type files is_global existing) ],
+);
+
+__PACKAGE__->run_before('check_object_params', only => [ qw(list ajax_delete ajax_importdialog ajax_import ajax_unimport ajax_upload ajax_files_uploaded) ]);
+
+my %file_types = (
+  'sales_quotation' =>         { gen => 1 ,gltype => '',   dir =>'SalesQuotation',       model => 'Order',          right => 'import_ar'  },
+  'sales_order'     =>         { gen => 1 ,gltype => '',   dir =>'SalesOrder',           model => 'Order',          right => 'import_ar'  },
+  'sales_delivery_order' =>    { gen => 1 ,gltype => '',   dir =>'SalesDeliveryOrder',   model => 'DeliveryOrder',  right => 'import_ar'  },
+  'invoice'         =>         { gen => 1 ,gltype => 'ar', dir =>'SalesInvoice',         model => 'Invoice',        right => 'import_ar'  },
+  'credit_note'     =>         { gen => 1 ,gltype => '',   dir =>'CreditNote',           model => 'Invoice',        right => 'import_ar'  },
+  'request_quotation' =>       { gen => 3 ,gltype => '',   dir =>'RequestForQuotation',  model => 'Order',          right => 'import_ap'  },
+  'purchase_order' =>          { gen => 3 ,gltype => '',   dir =>'PurchaseOrder',        model => 'Order',          right => 'import_ap'  },
+  'purchase_delivery_order' => { gen => 3 ,gltype => '',   dir =>'PurchaseDeliveryOrder',model => 'DeliveryOrder',  right => 'import_ap'  },
+  'purchase_invoice' =>        { gen => 2 ,gltype => 'ap', dir =>'PurchaseInvoice',      model => 'PurchaseInvoice',right => 'import_ap'  },
+  'vendor' =>                  { gen => 0 ,gltype => '',   dir =>'Vendor',               model => 'Vendor',         right => 'xx'  },
+  'customer' =>                { gen => 1 ,gltype => '',   dir =>'Customer',             model => 'Customer',       right => 'xx'  },
+  'part' =>                    { gen => 0 ,gltype => '',   dir =>'Part',                 model => 'Part',           right => 'xx'  },
+  'gl_transaction' =>          { gen => 2 ,gltype => 'gl', dir =>'GeneralLedger',        model => 'GLTransaction',  right => 'import_ap'  },
+  'draft' =>                   { gen => 0 ,gltype => '',   dir =>'Draft',                model => 'Draft',          right => 'xx'  },
+  'csv_customer' =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Customer',       right => 'xx'  },
+  'csv_vendor'   =>            { gen => 1 ,gltype => '',   dir =>'Reports',              model => 'Vendor',         right => 'xx'  },
+);
+
+#--- 4 locale ---#
+# $main::locale->text('imported')
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  my $isjson = 0;
+  $isjson = 1 if $::form->{json};
+
+  $self->_do_list($isjson);
+}
+
+sub action_ajax_importdialog {
+  my ($self) = @_;
+  $::auth->assert($self->object_right);
+  my $path   = $::form->{path};
+  my @files  = $self->_get_from_import($path);
+  my $source = {
+    'name'         => $::form->{source},
+    'path'         => $path ,
+    'chk_action'   => $::form->{source}.'_import',
+    'chk_title'    => $main::locale->text('Import scanned documents'),
+    'chkall_title' => $main::locale->text('Import all'),
+    'files'        => \@files
+  };
+  $self->render('file/import_dialog',
+                { layout => 0
+                },
+                source => $source
+  );
+}
+
+sub action_ajax_import {
+  my ($self) = @_;
+  $::auth->assert($self->object_right);
+  my $ids    = $::form->{ids};
+  my $source = $::form->{source};
+  my $path   = $::form->{path};
+  my @files = $self->_get_from_import($path);
+  foreach my $filename (@{ $::form->{$ids} || [] }) {
+    my ($file,undef) = grep { $_->{name} eq $filename } @files;
+    if ( $file ) {
+      my $obj = SL::File->save(object_id     => $self->object_id,
+                               object_type   => $self->object_type,
+                               mime_type     => 'application/pdf',
+                               source        => $source,
+                               file_type     => 'document',
+                               file_name     => $file->{filename},
+                               file_path     => $file->{path}
+                             );
+      unlink($file->{path}) if $obj;
+    }
+  }
+  $self->_do_list(1);
+}
+
+sub action_ajax_delete {
+  my ($self) = @_;
+  $self->_delete_all(DO_DELETE,$::locale->text('Following files are deleted:'));
+}
+
+sub action_ajax_unimport {
+  my ($self) = @_;
+  $self->_delete_all(DO_UNIMPORT,$::locale->text('Following files are unimported:'));
+}
+
+sub action_ajax_rename {
+  my ($self) = @_;
+  my $file = SL::File->get(id => $::form->{id});
+  if ( ! $file ) {
+    $self->js->flash('error',$::locale->text('File not exists !'))->render();
+    return;
+  }
+  $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$file->object_id." object_type=".$file->object_type." dbfile=".$file);
+  my $sessionfile = $::form->{sessionfile};
+      $main::lxdebug->message(LXDebug->DEBUG2(), "sessionfile=".$sessionfile);
+  if ( $sessionfile && -f $sessionfile ) {
+     $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file->file_name." to=".$::form->{to}." sessionfile=".$sessionfile);
+    # new uploaded file
+    if ( $::form->{to} eq $file->file_name ) {
+      # no rename so use as new version
+      $file->save_file($sessionfile);
+      $self->js->flash('warning',$::locale->text('File \'#1\' is used as new Version !',$file->file_name));
+
+    } else {
+      # new filename so it is a new file with same attributes as old file
+      eval {
+        SL::File->save(object_id     => $file->object_id,
+                       object_type   => $file->object_type,
+                       mime_type     => $file->mime_type,
+                       source        => $file->source,
+                       file_type     => $file->file_type,
+                       file_name     => $::form->{to},
+                       file_path     => $sessionfile
+                     );
+        unlink($sessionfile);
+        1;
+      } or do {
+        $self->js->flash(       'error', t8('internal error (see details)'))
+                 ->flash_detail('error', $@)->render;
+        return;
+      }
+    }
+
+  } else {
+    # normal rename
+    eval {
+      my $res = $file->rename($::form->{to});
+      $main::lxdebug->message(LXDebug->DEBUG2(), "rename result=".$res);
+      if ($res > SL::File::RENAME_OK) {
+        $self->js->flash('error',
+                         $res == SL::File::RENAME_EXISTS      ? $::locale->text('File still exists !')
+                       : $res == SL::File::RENAME_SAME        ? $::locale->text('Same Filename !')
+                       :                                        $::locale->text('File not exists !'))->render;
+        return 1;
+      }
+      1;
+    } or do {
+      $self->js->flash(       'error', t8('internal error (see details)'))
+               ->flash_detail('error', $@)->render;
+      return;
+    }
+  }
+  $self->is_global($::form->{is_global});
+  $self->file_type(  $file->file_type);
+  $self->object_type($file->object_type);
+  $self->object_id(  $file->object_id);
+  #$self->object_model($file_types{$file->module}->{model});
+  #$self->object_right($file_types{$file->module}->{right});
+  if ( $::form->{next_ids} ) {
+    my @existing = split(/,/, $::form->{next_ids});
+    $self->existing(\@existing);
+  }
+  $self->_do_list(1);
+}
+
+sub action_ajax_upload {
+  my ($self) = @_;
+  $self->{maxsize} = $::instance_conf->get_doc_max_filesize;
+  $self->{accept_types} = '';
+  $self->{accept_types} = 'image/png,image/gif,image/jpeg,image/tiff,*png,*gif,*.jpg,*.tif' if $self->{file_type} eq 'image';
+  $self->render('file/upload_dialog',
+                { layout          => 0
+                },
+  );
+}
+
+sub action_ajax_files_uploaded {
+  my ($self) = @_;
+
+  my $source = 'uploaded';
+  $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload UPLOAD=".$::form->{ATTACHMENTS}->{uploadfiles});
+  my @existing;
+  if ( $::form->{ATTACHMENTS}->{uploadfiles} ) {
+    my @upfiles = @{ $::form->{ATTACHMENTS}->{uploadfiles} };
+    foreach my $idx (0 .. scalar(@upfiles) - 1) {
+      eval {
+        my $fname = uri_unescape($upfiles[$idx]->{filename});
+        $main::lxdebug->message(LXDebug->DEBUG2(), "file_upload name=".$fname);
+        ## normalize and find basename
+        # first split with unix rules
+        # after that split with windows rules
+        my ($volume,$directories,$basefile) = File::Spec::Unix->splitpath($fname);
+        ($volume,$directories,$basefile) = File::Spec::Win32->splitpath($basefile);
+
+        # to find real mime_type by magic we must save the filedata
+
+        my $sess_fname = "file_upload_".$self->object_type."_".$self->object_id."_".$idx;
+        my $sfile     = SL::SessionFile->new($sess_fname, mode => 'w');
+
+        $sfile->fh->print(${$upfiles[$idx]->{data}});
+        $sfile->fh->close;
+        my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
+
+        if (! $mime_type) {
+          # if filename has the suffix "pdf", but is really no pdf set mimetype for no suffix
+          $mime_type = File::MimeInfo::Magic::mimetype($basefile);
+          $mime_type = 'application/octet-stream' if $mime_type eq 'application/pdf' || !$mime_type;
+        }
+        $main::lxdebug->message(LXDebug->DEBUG2(), "mime_type=".$mime_type);
+        if ( $self->file_type eq 'image' && $self->file_probe_image_type($mime_type, $basefile)) {
+          next;
+        }
+        my ($existobj) = SL::File->get_all(object_id     => $self->object_id,
+                                        object_type   => $self->object_type,
+                                        mime_type     => $mime_type,
+                                        source        => $source,
+                                        file_type     => $self->file_type,
+                                        file_name     => $basefile,
+                                      );
+
+        $main::lxdebug->message(LXDebug->DEBUG2(), "store1 exist=".$existobj);
+        if ($existobj) {
+  $main::lxdebug->message(LXDebug->DEBUG2(), "id=".$existobj->id." sessionfile=". $sfile->file_name);
+          push @existing, $existobj->id.'_'.$sfile->file_name;
+        } else {
+          my $fileobj = SL::File->save(object_id     => $self->object_id,
+                                       object_type   => $self->object_type,
+                                       mime_type     => $mime_type,
+                                       source        => $source,
+                                       file_type     => $self->file_type,
+                                       file_name     => $basefile,
+                                       ## two possibilities: what is better ? content or sessionfile ??
+                                       #file_contents => ${$upfiles[$idx]->{data}},
+                                       file_path     => $sfile->file_name
+                                     );
+          $main::lxdebug->message(LXDebug->DEBUG2(), "obj=".$fileobj);
+          unlink($sfile->file_name);
+        }
+        1;
+      } or do {
+        $self->js->flash(       'error', t8('internal error (see details)'))
+                 ->flash_detail('error', $@)->render;
+        return;
+      }
+    }
+  }
+  $self->existing(\@existing);
+  $self->_do_list(1);
+}
+
+sub action_download {
+  my ($self) = @_;
+  my ($id,$version) = split /_/, $::form->{id};
+  my $file = SL::File->get(id => $id );
+  $file->version($version) if $version;
+  my $ref  = $file->get_content;
+  if ( $file && $ref ) {
+    return $self->send_file($ref,
+      type => $file->mime_type,
+      name => $file->file_name,
+    );
+  }
+}
+
+#
+# filters
+#
+
+sub check_object_params {
+  my ($self) = @_;
+
+  my $id = $::form->{object_id} +0;
+  my $draftid = $::form->{draft_id} +0;
+  my $gldoc = 0;
+  my $type = undef;
+
+  if ( $draftid == 0 && $id == 0 && $::form->{is_global} ) {
+    $gldoc = 1;
+    $type = $::form->{object_type};
+  }
+  elsif ( $id == 0 ) {
+    $id = $::form->{draft_id};
+    $type = 'draft';
+  } elsif ( $::form->{object_type} ) {
+    $type = $::form->{object_type};
+  }
+  die "No object type"     if ! $type;
+  die "No file type"       if ! $::form->{file_type};
+  die "Unkown object type" if ! $file_types{$type};
+
+  $self->is_global($gldoc);
+  $self->file_type($::form->{file_type});
+  $self->object_type($type);
+  $self->object_id($id);
+  $self->object_model($file_types{$type}->{model});
+  $self->object_right($file_types{$type}->{right});
+  $main::lxdebug->message(LXDebug->DEBUG2(), "checked: object_id=".$self->object_id." object_type=".$self->object_type." is_global=".$self->is_global);
+
+ # $::auth->assert($self->object_right);
+
+ # my $model = 'SL::DB::' . $self->object_model;
+ # $self->object($model->new(id => $self->object_id)->load || die "Record not found");
+
+  return 1;
+}
+
+#
+# private methods
+#
+
+sub _delete_all {
+  my ($self,$do_unimport,$infotext) = @_;
+  my $files = '';
+  my $ids = $::form->{ids};
+  foreach my $id_version (@{ $::form->{$ids} || [] }) {
+    my ($id,$version) = split /_/, $id_version;
+    my $dbfile = SL::File->get(id => $id);
+    $dbfile->version($version) if $dbfile && $version;
+    if ( $dbfile && $dbfile->delete ) {
+      $files .= ' '.$dbfile->file_name;
+    }
+  }
+  $self->js->flash('info',$infotext.$files) if $files;
+  $self->_do_list(1);
+}
+
+sub _do_list {
+  my ($self,$json) = @_;
+  my @files;
+  $main::lxdebug->message(LXDebug->DEBUG2(), "do_list: object_id=".$self->object_id." object_type=".$self->object_type." file_type=".$self->file_type." json=".$json);
+  if ( $self->file_type eq 'document' ) {
+    @files   = SL::File->get_all_versions(object_id   => $self->object_id  ,
+                                          object_type => $self->object_type,
+                                          file_type   => $self->file_type  );
+
+    $main::lxdebug->message(LXDebug->DEBUG2(), "cnt1=".scalar(@files));
+  }
+  elsif ( $self->file_type eq 'attachment' ||  $self->file_type eq 'image' ) {
+    @files   = SL::File->get_all(object_id   => $self->object_id  ,
+                                 object_type => $self->object_type,
+                                 file_type   => $self->file_type  );
+    $main::lxdebug->message(LXDebug->DEBUG2(), "cnt2=".scalar(@files));
+  }
+  $self->files(\@files);
+  $self->_mk_render('file/list',1,0,$json);
+}
+
+sub _get_from_import {
+  my ($self,$path) = @_;
+  my @foundfiles ;
+
+  $main::lxdebug->message(LXDebug->DEBUG2(), "import path=".$path);
+  my $language = $::lx_office_conf{system}->{language};
+  my $timezone = $::locale->get_local_time_zone()->name;
+  if (opendir my $dir, $path) {
+    my @files = ( readdir $dir);
+    foreach my $file ( @files) {
+      next if (($file eq '.') || ($file eq '..'));
+      $file = Encode::decode('utf-8', $file);
+      $main::lxdebug->message(LXDebug->DEBUG2(), "file=".$file);
+
+      next if( -d "$path/$file");
+
+      my $tmppath = File::Spec->catfile( $path, $file );
+      $main::lxdebug->message(LXDebug->DEBUG2(), "tmppath=".$tmppath." file=".$file);
+      next if( ! -f $tmppath);
+
+      my $st = stat($tmppath);
+      my $dt = DateTime->from_epoch( epoch => $st->mtime, time_zone => $timezone, locale => $language);
+      my $sname = $main::locale->quote_special_chars('HTML',$file);
+      push @foundfiles , {
+        'name'     => $file,
+        'filename' => $sname,
+        'path'     => $tmppath,
+        'mtime'    => $st->mtime,
+        'date'     => $dt->dmy('.')." ".$dt->hms,
+      };
+
+    }
+  }
+  $main::lxdebug->message(LXDebug->DEBUG2(), "return ".scalar(@foundfiles)." files");
+  return @foundfiles;
+}
+
+sub _mk_render {
+  my ($self,$template,$edit,$scanner,$json) = @_;
+  my $err;
+  eval {
+    ##TODO  here a configurable code must be implemented
+
+    my $title;
+    $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: object_id=".$self->object_id." object_type=".$self->object_type.
+                              " file_type=".$self->file_type." json=".$json." filecount=".scalar(@{ $self->files })." is_global=".$self->is_global);
+    my @sources = $self->_get_sources();
+    foreach my $source ( @sources ) {
+      $main::lxdebug->message(LXDebug->DEBUG2(), "mk_render: source name=".$source->{name});
+      @{$source->{files}} = grep { $_->source eq $source->{name}} @{ $self->files };
+    }
+    if ( $self->file_type eq 'document' ) {
+      $title = $main::locale->text('Documents');
+    } elsif ( $self->file_type eq 'attachment' ) {
+      $title = $main::locale->text('Attachments');
+    } elsif ( $self->file_type eq 'image' ) {
+      $title = $main::locale->text('Images');
+    }
+
+    my $output         = SL::Presenter->get->render(
+      $template,
+      title             => $title,
+      SOURCES           => \@sources,
+      edit_attachments  => $edit,
+      object_type       => $self->object_type,
+      object_id         => $self->object_id,
+      file_type         => $self->file_type,
+      is_global         => $self->is_global,
+      json              => $json,
+    );
+    if ( $json ) {
+      $self->js->html('#'.$self->file_type.'_list_'.$self->object_type, $output);
+      if ( $self->existing && scalar(@{$self->existing}) > 0) {
+        my $first = shift @{$self->existing};
+        my ($first_id,$sfile) = split('_',$first,2);
+        #$main::lxdebug->message(LXDebug->DEBUG2(), "id=".$first_id." sessionfile=". $sfile);
+        my $file = SL::File->get(id => $first_id );
+        $self->js->run('kivi.File.askForRename',$first_id,$file->file_name,$sfile,join (',', @{$self->existing}), $self->is_global);
+      }
+      $self->js->render();
+    } else {
+        $self->render(\$output, { layout => 0, process => 0 });
+    }
+    1;
+  } or do {
+    if ($json ){
+      $self->js->flash(       'error', t8('internal error (see details)'))
+               ->flash_detail('error', $@)->render;
+    } else {
+      $self->render('generic/error', { layout => 0 }, label_error => $@);
+    }
+  };
+}
+
+
+sub _get_sources {
+  my ($self) = @_;
+  my @sources;
+  $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources file_type=". $self->file_type);
+  if ( $self->file_type eq 'document' ) {
+    ##TODO statt gen neue attribute in filetypes :
+    if (($file_types{$self->object_type}->{gen}*1 & 1)==1) {
+      my $gendata = {
+        'name'         => 'created',
+        'title'        => $main::locale->text('generated Files'),
+        'chk_action'   => 'documents_delete',
+        'chk_title'    => $main::locale->text('Delete Documents'),
+        'chkall_title' => $main::locale->text('Delete all'),
+        'file_title'   => $main::locale->text('filename'),
+        'confirm_text' => $main::locale->text('delete'),
+        'can_rename'   => 1,
+        'rename_title' => $main::locale->text('Rename Documents'),
+        'done_text'    => $main::locale->text('deleted')
+      };
+      push @sources , $gendata;
+    }
+    if (($file_types{$self->object_type}->{gen}*1 & 2)==2) {
+      my @others =  SL::File->get_other_sources();
+      $main::lxdebug->message(LXDebug->DEBUG2(), "other cnt=". scalar(@others));
+      foreach my $scanner_or_mailrx (@others) {
+        my $other = {
+          'name'         => $scanner_or_mailrx->{name},
+          'title'        => $main::locale->text('from \'#1\' imported Files',$scanner_or_mailrx->{description}),
+          'chk_action'   => $scanner_or_mailrx->{name}.'_unimport',
+          'chk_title'    => $main::locale->text('Unimport documents'),
+          'chkall_title' => $main::locale->text('Unimport all'),
+          'file_title'   => $main::locale->text('filename'),
+          'confirm_text' => $main::locale->text('unimport'),
+          'can_rename'   => 1,
+          'rename_title' => $main::locale->text('Rename Documents'),
+          'can_import'   => 1,
+          'import_title' => $main::locale->text('Add Document from \'#1\'',$scanner_or_mailrx->{name}),
+          'path'         => $scanner_or_mailrx->{directory},
+          'done_text'    => $main::locale->text('unimported')
+        };
+        push @sources , $other;
+      }
+    }
+  }
+  elsif ( $self->file_type eq 'attachment' ) {
+    my $attdata = {
+      'name'         => 'uploaded',
+      'title'        => $main::locale->text(''),
+      'chk_action'   => 'attachments_delete',
+      'chk_title'    => $main::locale->text('Delete Attachments'),
+      'chkall_title' => $main::locale->text('Delete all'),
+      'file_title'   => $main::locale->text('filename'),
+      'confirm_text' => $main::locale->text('delete'),
+      'can_rename'   => 1,
+      'are_existing' => $self->existing ? 1 : 0,
+      'rename_title' => $main::locale->text('Rename Attachments'),
+      'can_upload'   => 1,
+      'upload_title' => $main::locale->text('Upload Attachments'),
+      'done_text'    => $main::locale->text('deleted')
+    };
+    push @sources , $attdata;
+  }
+  elsif ( $self->file_type eq 'image' ) {
+    my $attdata = {
+      'name'         => 'uploaded',
+      'title'        => $main::locale->text(''),
+      'chk_action'   => 'images_delete',
+      'chk_title'    => $main::locale->text('Delete Images'),
+      'chkall_title' => $main::locale->text('Delete all'),
+      'file_title'   => $main::locale->text('filename'),
+      'confirm_text' => $main::locale->text('delete'),
+      'can_rename'   => 1,
+      'are_existing' => $self->existing ? 1 : 0,
+      'rename_title' => $main::locale->text('Rename Images'),
+      'can_upload'   => 1,
+      'upload_title' => $main::locale->text('Upload Images'),
+      'done_text'    => $main::locale->text('deleted')
+    };
+    push @sources , $attdata;
+  }
+  $main::lxdebug->message(LXDebug->DEBUG2(), "get_sources count=".scalar(@sources));
+  return @sources;
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Controller::File - Controller for managing files
+
+
+=head1 SYNOPSIS
+
+=begin text
+
+    # The Controller is called direct from the webpages
+
+
+    <a href="controller.pl?action=File/list&file_type=document\
+       &object_type=[% HTML.escape(type) %]&object_id=[% HTML.url(id) %]">
+
+
+    # or indirect via javascript functions from js/kivi.File.js
+
+
+    kivi.popup_dialog({ url:     'controller.pl',
+                        data:    { action     : 'File/ajax_upload',
+                                   file_type  : 'uploaded',
+                                   object_type: type,
+                                   object_id  : id
+                                 }
+                           ...
+
+=end text
+
+
+=head1 DESCRIPTION
+
+This is a controller for handling files in a storage independant way.
+The storage may be a Filesystem,a WebDAV, a Database or DMS.
+These backends must be configered in ClientConfig.
+This Controller use as intermediate layer for storage C<SL::File>.
+
+The Controller is responsible to display forms for displaying the files at the ERP-objects and
+for uploading and downloading the files.
+
+More description of the intermediate layer see L<SL::File>.
+
+=head1 METHODS
+
+=head2 C<action_list>
+
+This loads a list of files on a webpage. This can be done with a normal submit or via an ajax/json call.
+Dependant of file_type different sources are available.
+
+For documents there are the 'created' source and the imports from scanners or email.
+For attachments and images only the 'uploaded' source available.
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.object_id>
+
+The Id of the ERP-object.
+
+=item C<form.object_type>
+
+The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller.
+
+=item C<form.file_type>
+
+For one ERP-object may exists different type of documents the type may be "documents","attachments" or "images".
+This file_type is a filter for the list.
+
+=item C<form.json>
+
+The method can be used as normal HTTP-Request (json=0) or as AJAX-JSON call to refresh the list if the parameter is set to 1.
+
+=back
+
+
+=head2 C<action_ajax_upload>
+
+
+A new file or more files can selected by a dialog and insert into the system.
+
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.file_type>
+
+This parameter describe here the source for a new file :
+"attachments" and "images"
+
+This is a normal upload selection, which may be more then one file to upload.
+
+=item C<form.object_id>
+
+and
+
+=item C<form.object_type>
+
+are the same as at C<action_list>
+
+=back
+
+=head2  C<action_ajax_files_uploaded>
+
+The Upload of selected Files. The "multipart_formdata" is parsed in SL::Request into the formsvariable "form.ATTACHMENTS".
+The filepaths are checked about Unix and Windows paths. Also the MIME type of the files are verified ( IS the contents of a *.pdf real PDF?).
+If the same filename still exists at this object after the download for each existing filename a rename dialog will be opened.
+
+If the filename is not changed the new uploaded file is a new version of the file, if the name is changed it is a new file.
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.ATTACHMENTS.uploadfiles>
+
+This is an array of elements which have {filename} for the name and {data} for the contents.
+
+Also object_id, object_type and file_type
+
+=back
+
+=head2 C<action_download>
+
+This is the real download of a file normally called via javascript "$.download("controller.pl", data);"
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+Also object_id, object_type and file_type
+
+=back
+
+=head2 C<action_ajax_importdialog>
+
+A Dialog with all available and not imported files to import is open.
+More then one file can be selected.
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.source>
+
+The name of the source like "scanner1" or "email"
+
+=item C<form.path>
+
+The full path to the directory on the server, where the files to import can found
+
+Also object_id, object_type and file_type
+
+=back
+
+=head2 C<action_ajax_delete>
+
+Some files can be deleted
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.ids>
+
+The ids of the files to delete. Only this files are deleted not all versions of a file if the exists
+
+=back
+
+=head2 C<action_ajax_unimport>
+
+Some files can be unimported, dependant of the source of the file. This means they are moved
+back to the directory of the source
+
+Available C<FORM PARAMS>:
+
+=over 4
+
+=item C<form.ids>
+
+The ids of the files to unimport. Only this files are unimported not all versions of a file if the exists
+
+=back
+
+=head2 C<action_ajax_rename>
+
+One file can be renamed. There can be some checks if the same filename still exists at one object.
+
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
+=cut
+
diff --git a/SL/Controller/Helper/ThumbnailCreator.pm b/SL/Controller/Helper/ThumbnailCreator.pm
new file mode 100644 (file)
index 0000000..072d89d
--- /dev/null
@@ -0,0 +1,144 @@
+package SL::Controller::Helper::ThumbnailCreator;
+
+use strict;
+
+use SL::Locale::String qw(t8);
+use Carp;
+use GD;
+use Image::Info;
+use File::MimeInfo::Magic;
+use List::MoreUtils qw(apply);
+use List::Util qw(max);
+use Rose::DB::Object::Util;
+
+require Exporter;
+our @ISA      = qw(Exporter);
+our @EXPORT   = qw(file_create_thumbnail file_update_thumbnail file_probe_type file_probe_image_type file_update_type_and_dimensions);
+
+# TODO PDFs and others like odt,txt,...
+our %supported_mime_types = (
+  'image/gif'  => { extension => 'gif', convert_to_png => 1, },
+  'image/png'  => { extension => 'png' },
+  'image/jpeg' => { extension => 'jpg' },
+  'image/tiff' => { extension => 'tif'},
+);
+
+sub file_create_thumbnail {
+  my ($self) = @_;
+  croak "No picture set yet" if !$self->file_content;
+
+  my $image            = GD::Image->new($self->file_content);
+  my ($width, $height) = $image->getBounds;
+  my $max_dim          = 64;
+  my $curr_max         = max $width, $height, 1;
+  my $factor           = $curr_max <= $max_dim ? 1 : $curr_max / $max_dim;
+  my $new_width        = int($width  / $factor + 0.5);
+  my $new_height       = int($height / $factor + 0.5);
+  my $thumbnail        = GD::Image->new($new_width, $new_height);
+
+  $thumbnail->copyResized($image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
+
+  $self->thumbnail_img_content($thumbnail->png);
+  $self->thumbnail_img_content_type('image/png');
+  $self->thumbnail_img_width($new_width);
+  $self->thumbnail_img_height($new_height);
+  return 1;
+
+}
+
+sub file_update_thumbnail {
+  my ($self) = @_;
+
+  return 1 if !$self->file_content || !$self->file_content_type || !Rose::DB::Object::Util::get_column_value_modified($self, 'file_content');
+  $self->file_create_thumbnail;
+  return 1;
+}
+
+sub file_probe_image_type {
+  my ($self, $mime_type, $basefile) = @_;
+
+  if ( !$supported_mime_types{ $mime_type } ) {
+    $self->js->flash('error',t8('file \'#1\' has unsupported image type \'#2\' (supported types: #3)',
+                                $basefile, $mime_type, join(' ', sort keys %supported_mime_types)));
+    return 1;
+  }
+  return 0;
+}
+
+sub file_probe_type {
+  my ($self) = @_;
+
+  return (t8("No file uploaded yet")) if !$self->file_content;
+  my $mime_type = File::MimeInfo::Magic::magic($self->file_content);
+
+  my $info = Image::Info::image_info(\$self->{file_content});
+  if (!$info || $info->{error} || !$info->{file_media_type} || !$supported_mime_types{ $info->{file_media_type} }) {
+    $::lxdebug->warn("Image::Info error: " . $info->{error}) if $info && $info->{error};
+    return (t8('Unsupported image type (supported types: #1)', join(' ', sort keys %supported_mime_types)));
+  }
+
+  $self->file_content_type($info->{file_media_type});
+  $self->files_img_width($info->{width});
+  $self->files_img_height($info->{height});
+  $self->files_mtime(DateTime->now_local);
+
+  $self->file_create_thumbnail;
+
+  return ();
+}
+
+sub file_update_type_and_dimensions {
+  my ($self) = @_;
+
+  return () if !$self->file_content;
+  return () if $self->file_content_type && $self->files_img_width && $self->files_img_height && !Rose::DB::Object::Util::get_column_value_modified($self, 'file_content');
+
+  my @errors = $self->file_probe_type;
+  return @errors if @errors;
+
+  my $info = $supported_mime_types{ $self->file_content_type };
+  if ($info->{convert_to_png}) {
+    $self->file_content(GD::Image->new($self->file_content)->png);
+    $self->file_content_type('image/png');
+    $self->filename(apply { s/\.[^\.]+$//;  $_ .= '.png'; } $self->filename);
+  }
+  return ();
+}
+
+1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+  SL::DB::Helper::ThumbnailCreator - DatabaseClass Helper for Fileuploads
+
+=head1 SYNOPSIS
+
+  use SL::DB::Helper::ThumbnailCreator;
+
+  # synopsis...
+
+=head1 DESCRIPTION
+
+  # longer description..
+
+=head1 AUTHOR
+
+  Werner Hahn E<lt>wh@futureworldsearch.netE<gt>
+
+=cut
+
+
+=head1 INTERFACE
+
+
+=head1 DEPENDENCIES
+
+
+=head1 SEE ALSO
+
+
index 344f1f0..f50a121 100644 (file)
@@ -84,6 +84,8 @@ use URI;
 use List::Util qw(first max min sum);
 use List::MoreUtils qw(all any apply);
 use SL::DB::Tax;
+use SL::Helper::File qw(:all);
+use SL::Helper::CreatePDF qw(merge_pdfs);
 
 use strict;
 
@@ -1076,9 +1078,18 @@ sub parse_template {
   # therefore copy to webdav, even if we do not have the webdav feature enabled (just archive)
   my $copy_to_webdav =  $::instance_conf->get_webdav_documents && !$self->{preview} && $self->{tmpdir} && $self->{tmpfile} && $self->{type};
 
+  if ( $ext_for_format eq 'pdf' && $::instance_conf->get_doc_storage ) {
+    $self->append_general_pdf_attachments(filepath =>  $self->{tmpdir}."/".$self->{tmpfile},
+                                          type     =>  $self->{type});
+  }
   if ($self->{media} eq 'file') {
     copy(join('/', $self->{cwd}, $userspath, $self->{tmpfile}), $out =~ m|^/| ? $out : join('/', $self->{cwd}, $out)) if $template->uses_temp_file;
     Common::copy_file_to_webdav_folder($self)                                                                         if $copy_to_webdav;
+    if (!$self->{preview} && $::instance_conf->get_doc_storage)
+    {
+      $self->{attachment_filename} ||= $self->generate_attachment_filename;
+      $self->store_pdf($self);
+    }
     $self->cleanup;
     chdir("$self->{cwd}");
 
@@ -1089,6 +1100,10 @@ sub parse_template {
 
   Common::copy_file_to_webdav_folder($self) if $copy_to_webdav;
 
+  if ( !$self->{preview} && $ext_for_format eq 'pdf' && $::instance_conf->get_doc_storage) {
+    $self->{attachment_filename} ||= $self->generate_attachment_filename;
+    $self->store_pdf($self);
+  }
   if ($self->{media} eq 'email') {
 
     my $mail = Mailer->new;
@@ -2453,7 +2468,6 @@ sub get_name {
 }
 
 sub new_lastmtime {
-  $main::lxdebug->enter_sub();
 
   my ($self, $table, $provided_dbh) = @_;
 
@@ -2465,9 +2479,7 @@ sub new_lastmtime {
   my $ref         = selectfirst_hashref_query($self, $dbh, $query, $self->{id});
   $ref->{mtime} ||= $ref->{itime};
   $self->{lastmtime} = $ref->{mtime};
-  $main::lxdebug->message(LXDebug->DEBUG2(),"new lastmtime=".$self->{lastmtime});
 
-  $main::lxdebug->leave_sub();
 }
 
 sub mtime_ischanged {
@@ -3000,6 +3012,7 @@ sub save_status {
 
 #--- 4 locale ---#
 # $main::locale->text('SAVED')
+# $main::locale->text('SCREENED')
 # $main::locale->text('DELETED')
 # $main::locale->text('ADDED')
 # $main::locale->text('PAYMENT POSTED')
@@ -3012,6 +3025,8 @@ sub save_status {
 # $main::locale->text('MAILED')
 # $main::locale->text('SCREENED')
 # $main::locale->text('CANCELED')
+# $main::locale->text('IMPORT')
+# $main::locale->text('UNIMPORT')
 # $main::locale->text('invoice')
 # $main::locale->text('proforma')
 # $main::locale->text('sales_order')
diff --git a/SL/Helper/File.pm b/SL/Helper/File.pm
new file mode 100644 (file)
index 0000000..7b31ebd
--- /dev/null
@@ -0,0 +1,151 @@
+package SL::Helper::File;
+
+use strict;
+
+use Exporter 'import';
+our @EXPORT_OK = qw(store_pdf append_general_pdf_attachments);
+our %EXPORT_TAGS = (all => \@EXPORT_OK,);
+use SL::File;
+
+sub store_pdf {
+  my ($self, $form) = @_;
+  return unless $::instance_conf->get_doc_storage;
+  my $type = $form->{type};
+  $type = $form->{formname} if $form->{formname} && !$form->{type};
+  my $id = $form->{id};
+  $id = $form->{attachment_id} if $form->{attachment_id} && !$form->{id};
+  return if !$id || !$type;
+  my $prefix = $form->get_number_prefix_for_type();
+  SL::File->save(
+    object_id   => $id,
+    object_type => $type,
+    mime_type   => 'application/pdf',
+    source      => 'created',
+    file_type   => 'document',
+    file_name   => $form->{attachment_filename},
+    file_path   => $form->{tmpfile},
+    file_number => $form->{"${prefix}number"},
+  );
+}
+
+# This method also needed by $form to append all general pdf attachments
+#
+sub append_general_pdf_attachments {
+  my ($self, %params) = @_;
+  return 0 unless $::instance_conf->get_doc_storage;
+  return 0 if !$params{filepath} || !$params{type};
+
+  my @files = SL::File->get_all(
+    object_id   => 0,
+    object_type => $params{type},
+    mime_type   => 'application/pdf'
+  );
+  return 0 if $#files < 0;
+
+  my @pdf_file_names = ($params{filepath});
+  foreach my $file (@files) {
+    my $path = $file->get_file;
+    push @pdf_file_names, $path if $path;
+  }
+
+  #TODO immer noch das alte Problem:
+  #je nachdem von woher der Aufruf kommt ist man in ./users oder .
+  my $savedir = POSIX::getcwd();
+  chdir("$self->{cwd}");
+  $self->merge_pdfs(
+    file_names => \@pdf_file_names,
+    out_path   => $params{filepath}
+  );
+  chdir("$savedir");
+
+  return 0;
+}
+
+1;
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::File - Helper for $::Form to store generated PDF-Documents
+
+
+=head1 SYNOPSIS
+
+# This Helper is used by SL::Form to store new generated PDF-Files and append general attachments to this documents.
+#
+# in SL::Form.pm:
+
+ $self->store_pdf($self);
+
+ $self->append_general_pdf_attachments($self) if ( $ext_for_format eq 'pdf' );
+
+=head1 DESCRIPTION
+
+The files with file_type "generated" are stored.
+
+See also L<SL::File>.
+
+=head1 METHODS
+
+
+=head2 C<store_pdf>
+
+Copy generated PDF-File to File destination.
+This method is need from SL::Form after LaTeX-PDF Generation
+
+=over 4
+
+=item C<form.id>
+
+ID of ERP-Document
+
+=item C<form.type>
+
+type of ERP-document
+
+=item C<form.formname>
+
+if no type is set this is used as type
+
+=item C<form.attachment_id>
+
+if no id is set this is used as id
+
+=item C<form.tmpfile>
+
+The path of the generated PDF-file
+
+=item C<form.attachment_filename>
+
+The generated filename which is used as new filename (without timestamp)
+
+=back
+
+=head2 C<append_general_pdf_attachments PARAMS>
+
+This method also needed by SL::Form to append all general pdf attachments
+
+needed C<PARAMS>:
+
+=over 4
+
+=item C<type>
+
+type of ERP-document
+
+=item C<outname>
+
+Name of file to which the general attachments must be added
+
+=back
+
+=head1 AUTHOR
+
+Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt>
+
+
+=cut
+
diff --git a/js/kivi.File.js b/js/kivi.File.js
new file mode 100644 (file)
index 0000000..036cefe
--- /dev/null
@@ -0,0 +1,242 @@
+namespace('kivi.File', function(ns) {
+
+  ns.rename = function(id,type,file_type,checkbox_class,is_global) {
+    var checkboxes = $('.'+checkbox_class).filter(function () { return  $(this).prop('checked'); });
+
+    if (checkboxes.size() === 0) {
+                 alert(kivi.t8("No file selected, please set one checkbox!"));
+                 return false;
+         }
+    if (checkboxes.size() > 1) {
+           alert(kivi.t8("More than one file selected, please set only one checkbox!"));
+                 return false;
+         }
+    var file_id = checkboxes[0].value;
+    $('#newfilename_id').val($('#filename_'+file_id).text());
+    $('#next_ids_id').val('');
+    $('#is_global_id').val(is_global);
+    $('#rename_id_id').val(file_id);
+    $('#sessionfile_id').val('');
+       $('#rename_extra_text').html('');
+    kivi.popup_dialog({
+                      id:     'rename_dialog',
+                      dialog: { title: kivi.t8("Rename attachment")
+                               , width:  400
+                               , height: 200
+                               , modal:  true } });
+    return true;
+  }
+  
+  ns.renameclose = function() {
+    $("#rename_dialog").dialog('close');
+    return false;
+  }
+  
+  ns.renameaction = function() {
+         $("#rename_dialog").dialog('close');
+    var data = {
+      action:          'File/ajax_rename',
+      id:              $('#rename_id_id').val(),
+           to:              $('#newfilename_id').val(),
+      next_ids:        $('#next_ids_id').val(),
+      is_global:       $('#is_global_id').val(),
+      sessionfile:     $('#sessionfile_id').val(),
+    };
+    $.post("controller.pl", data, kivi.eval_json_result);
+    return true;
+  }
+  
+  ns.askForRename = function(file_id,file_name,sessionfile,next_ids,is_global) {
+    $('#newfilename_id').val(file_name);
+    $('#rename_id_id').val(file_id);
+    $('#is_global_id').val(is_global);
+    $('#next_ids_id').val(next_ids);
+    $('#sessionfile_id').val(sessionfile);
+    $('#rename_extra_text').html(kivi.t8("The uploaded filename still exists.<br>If you not modify the name this is a new version of the file"));
+    kivi.popup_dialog(
+      {
+        id:     'rename_dialog',
+        dialog: { title: kivi.t8("Rename attachment")
+                  , width:  400
+                  , height: 200
+                  , modal:  true }
+      });
+  }
+
+  ns.upload = function(id,type,filetype,upload_title,gl) {
+    kivi.popup_dialog({ url:     'controller.pl',
+                        data:    { action: 'File/ajax_upload',
+                                   file_type:   filetype,
+                                   object_type: type,
+                                   object_id:   id,
+                                   is_global:   gl
+                                 },
+                        id:     'files_upload',
+                        dialog: { title: upload_title, width: 650, height: 240 } });
+    return true;
+  }
+
+  ns.reset_upload_form = function() {
+      $('#attachment_updfile').val('');
+      $("#upload_result").html('');
+      ns.allow_upload_submit();
+  }
+
+  ns.allow_upload_submit = function() {
+      $('#upload_selected_button').prop('disabled',$('#upload_files').val() === '');
+  }
+
+  ns.upload_selected_files = function(id,type,filetype,maxsize,is_global) {
+      var myform = document.getElementById("upload_form");
+      var filesize  = 0;
+      var myfiles = document.getElementById("upload_files").files;
+      for ( i=0; i < myfiles.length; i++ ) {
+          var fname ='';
+          try {
+              filesize  += myfiles[i].size;
+              fname = encodeURIComponent(myfiles[i].name);
+          }
+          catch(err) {
+              fname ='';
+              try {
+                  fname = myfiles[i].name;
+              }
+              catch(err2) { fname ='';}
+              $("#upload_result").html(kivi.t8("filename has not uploadable characters ")+fname);
+              return;    
+          }
+      }
+      if ( filesize > maxsize ) {
+          $("#upload_result").html(kivi.t8("filesize too big: ")+
+                                   filesize+ kivi.t8(" bytes, max=") + maxsize );
+          return;
+      }
+
+      myform.action ="controller.pl?action=File/ajax_files_uploaded&json=1&object_type="+
+          type+'&object_id='+id+'&file_type='+filetype+'&is_global='+is_global;
+      var oReq = new XMLHttpRequest();
+      oReq.onload            = ns.attSuccess;
+      oReq.upload.onprogress = ns.attProgress;
+      oReq.upload.onerror    = ns.attFailed;
+      oReq.upload.onabort    = ns.attCanceled;
+      oReq.open("post",myform.action, true);
+      $("#upload_result").html(kivi.t8("start upload"));
+      oReq.send(new FormData(myform));
+  }
+
+  ns.attProgress = function(oEvent) {
+      if (oEvent.lengthComputable) {
+          var percentComplete = (oEvent.loaded / oEvent.total) * 100;
+          $("#upload_result").html(percentComplete+" % "+ kivi.t8("uploaded"));
+      }
+  }
+
+  ns.attFailed = function(evt) {
+      $('#files_upload').dialog('close');
+      $("#upload_result").html(kivi.t8("An error occurred while transferring the file."));
+  }
+
+  ns.attCanceled = function(evt) {
+      $('#files_upload').dialog('close');
+      $("#upload_result").html(kivi.t8("The transfer has been canceled by the user."));
+  }
+
+  ns.attSuccess = function() {
+      $('#files_upload').dialog('close');
+      kivi.eval_json_result(jQuery.parseJSON(this.response));
+  }
+
+  ns.delete = function(id,type,file_type,checkbox_class,is_global) {
+    var checkboxes = $('.'+checkbox_class).filter(function () { return  $(this).prop('checked'); });
+
+    if ((checkboxes.size() === 0) || 
+        !confirm(kivi.t8('Do you really want to delete the selected documents?')))
+      return false;
+    var data = {
+      action     :  'File/ajax_delete',
+      object_id  :  id,
+      object_type:  type,
+      file_type  :  file_type,
+      ids        :  checkbox_class,
+      is_global  :  is_global,
+    };
+    $.post("controller.pl?" + checkboxes.serialize(), data, kivi.eval_json_result);
+    return false;
+  }
+
+  ns.unimport = function(id,type,file_type,checkbox_class) {
+    var checkboxes = $('.'+checkbox_class).filter(function () { return  $(this).prop('checked'); });
+
+    if ((checkboxes.size() === 0) || 
+        !confirm(kivi.t8('Do you really want to unimport the selected documents?')))
+      return false;
+    var data = {
+      action     :  'File/ajax_unimport',
+      object_id  :  id,
+      object_type:  type,
+      file_type  :  file_type,
+      ids        :  checkbox_class,
+    };
+    $.post("controller.pl?" + checkboxes.serialize(), data, kivi.eval_json_result);
+    return false;
+  }
+
+  ns.update = function(id,type,file_type,is_global) {
+    var data = {
+      action:       'File/list',
+      json:         1,
+      object_type:  type,
+      object_id:    id,
+      file_type:    file_type,
+      is_global:    is_global
+    };
+
+    $.post("controller.pl", data, kivi.eval_json_result);
+    return false;
+  }
+
+  ns.import = function (id,type,file_type,fromwhere,frompath) {
+    kivi.popup_dialog({ url:     'controller.pl',
+                        data:    { action      : 'File/ajax_importdialog',
+                                   object_type : type,
+                                   source      : fromwhere,
+                                   path        : frompath,
+                                   file_type   : file_type,
+                                   object_id   : id
+                                 },
+                        id:     'import_dialog',
+                        dialog: { title: kivi.t8('Import documents from #1',[fromwhere]), width: 420, height: 540 }
+                      });
+    return true;
+  }
+    
+  ns.importclose = function() {
+    $("#import_dialog").dialog('close');
+    return false;
+  }
+  
+  ns.importaction = function(id,type,file_type,fromwhere,frompath,checkbox_class) {
+    var checkboxes = $('.'+checkbox_class).filter(function () { return  $(this).prop('checked'); });
+
+         $("#import_dialog").dialog('close');
+    if (checkboxes.size() === 0) {
+                 return false;
+         }
+       var data = {
+        action     : 'File/ajax_import',
+        object_id  : id,
+        object_type: type,
+        file_type  : file_type,
+        source     : fromwhere,
+        path       : frompath,
+        ids        : checkbox_class
+    };
+    $.post("controller.pl?" + checkboxes.serialize(), data, kivi.eval_json_result);
+    return true;
+  }
+
+
+  ns.init = function() {
+  }
+
+});
index 052b63e..813284a 100644 (file)
@@ -1475,6 +1475,8 @@ $self->{texts} = {
   'Illegal date'                => 'Ungültiges Datum',
   'Image'                       => 'Grafik',
   'Import'                      => 'Import',
+  'Import AP from Scanner or Email' => 'Einkaufsbelege importieren vom Scanner oder von Email',
+  'Import AR from Scanner or Email' => 'Verkaufsbelege importieren vom Scanner oder von Email',
   'Import CSV'                  => 'CSV-Import',
   'Import Status'               => 'Import Status',
   'Import a MT940 file:'        => 'Laden Sie eine MT940 Datei hoch:',
diff --git a/sql/Pg-upgrade2-auth/other_file_sources.sql b/sql/Pg-upgrade2-auth/other_file_sources.sql
new file mode 100644 (file)
index 0000000..1f26477
--- /dev/null
@@ -0,0 +1,7 @@
+-- @tag: other_file_sources
+-- @description: Neue Gruppenrechte für das Importieren von Scannern oder email
+-- @depends: release_3_4_0 master_rights_position_gaps
+-- @locales: Import AP from Scanner or Email
+-- @locales: Import AR from Scanner or Email
+INSERT INTO auth.master_rights (position, name, description) VALUES (2050, 'import_ar', 'Import AR from Scanner or Email');
+INSERT INTO auth.master_rights (position, name, description) VALUES (2650, 'import_ap', 'Import AP from Scanner or Email');
diff --git a/templates/webpages/file/import_dialog.html b/templates/webpages/file/import_dialog.html
new file mode 100644 (file)
index 0000000..48463ee
--- /dev/null
@@ -0,0 +1,36 @@
+[%- USE L -%][%- USE LxERP -%][%- USE JavaScript -%]
+
+<form method="post" id="file_import_form" action="controller.pl">
+ <table>
+  <thead>
+   <tr>
+    <th class="listheading" width="3%">[% L.checkbox_tag(source.chk_action _ '_checkall') %]</th>
+    <th class="listheading" width="11%">[% source.chkall_title %]</th>
+     <th>[% LxERP.t8("Attached Filename") %]</th>
+   </tr>
+  </thead>
+  <tbody>
+   [%- FOREACH file = source.files %]
+   <tr class="listrow[% loop.count % 2 %]">
+   <td>[%- L.checkbox_tag(source.chk_action _ '[]', 'value'=file.name, 'class'=source.chk_action) %]</td>
+   <td></td>
+   <td><span id="[% "filename_" _ file.name %]">[% file.filename %]</span></td>
+   </tr>
+   [%- END %]
+  </tbody>
+ </table>
+
+ <p>
+   [%- L.button_tag("kivi.File.importaction(" _ SELF.object_id _ ",'" _ SELF.object_type _ "','" _ SELF.file_type _ "','" _ source.name _ "','" _ source.path _ "','"_ source.chk_action _ "');", LxERP.t8('Continue'), id => "import_cont_btn") %]</td>
+   [%- L.button_tag("kivi.File.importclose();" , LxERP.t8('Cancel')  , class => "submit") %]</td></tr>
+ </p>
+
+</form>
+
+<script type="text/javascript">
+<!--
+$(function() {
+  $('#[% source.chk_action %]_checkall').checkall('INPUT[name="[% source.chk_action %][]"]');
+});
+-->
+</script>
diff --git a/templates/webpages/file/list.html b/templates/webpages/file/list.html
new file mode 100644 (file)
index 0000000..238fb12
--- /dev/null
@@ -0,0 +1,101 @@
+[%- USE LxERP -%][% USE L %][% USE HTML %]
+[%- IF ! json %]
+<div id="[% file_type %]_list_[% object_type %]">
+[%- END %]
+<div class="listtop">[% title %]</div>
+
+<div style="padding-bottom: 15px">
+[%- SET can_rename = 0 %]
+[%- FOREACH source = SOURCES %]
+ <table style="width: 100%" >
+  <thead>
+   <tr><th class="listheading" colspan="6">[% source.title %]</th></tr>
+   <tr>
+  [%- SET checkname = source.chk_action %]
+  [%- IF is_global %]
+  [%- SET checkname = object_type _ '_' _ source.chk_action %]
+  [%- END %]
+  [%- IF edit_attachments %]
+ <script type="text/javascript">
+  <!--
+$(function() {
+  $('#[% checkname %]_checkall').checkall('INPUT[name="[% checkname %][]"]');
+});
+ -->
+</script>
+    <th class="listheading" width="3%">[% L.checkbox_tag(checkname _ '_checkall') %]</th>
+    <th class="listheading" width="7%">[% source.chkall_title %]</th>
+  [%- END %]
+    <th class="listheading" width="15%"><b>[%  LxERP.t8('Date') %]</b></th>
+    <th class="listheading" width="20%"><b>[%  source.file_title %]</b></th>
+  [%- IF file_type == 'image' %]
+    <th class="listheading" width="15%"><b>[%  LxERP.t8('Title') %]</b></th>
+    <th class="listheading" width="10%">
+    <b>[%  LxERP.t8('ImagePreview') %]</b>
+    </th>
+    <th class="listheading" width="30%"><b>[%  LxERP.t8('Description') %]</b></th>
+  [%- ELSE %]
+    <th class="listheading" width="40%"></th>
+  [%- END %]
+   </tr>
+  </thead>
+
+  <tbody>
+  [%- FOREACH file = source.files %]
+   <tr class="listrow[% loop.count % 2 %]">
+    [%- IF edit_attachments %]
+    [%- IF file.newest %]
+    <td>[%- L.checkbox_tag(checkname _ '[]', 'value'=file.id, 'class'=checkname) %]</td>
+    [%- ELSE %]
+    <td></td>
+    [%- END %]
+    <td></td>
+    [%- END %]
+    <td>[% file.mtime_as_timestamp_s %]</td>
+    <td><a href="controller.pl?action=File/download&id=[% file.id %][%- IF file.version %]&version=[%- file.version %][%- END %]">
+        <span id="[% "filename_" _ file.id %][%- IF file.version %]_[% file.version %][%- END %]">[% file.file_name %]</span></a></td>
+    [%- IF file_type == 'image' %]
+    <td>[% file.title %]</td>
+    <td>
+      <img src="controller.pl?action=File/download&id=[% file.id %][%- IF file.version %]&version=[%- file.version %][%- END %]" alt="[% file.title %]" width="64px">
+    </td>
+    <td>[% file.description %]</td>
+    [%- ELSE %]
+    <td></td>
+    [%- END %]
+   </tr>
+  [%- END %]
+  </tbody>
+ </table
+  <div>
+  [%- IF edit_attachments %]
+    [%- IF source.can_import %]
+      [% L.button_tag("kivi.File.unimport(" _ object_id _ ",'" _ object_type _ "','" _ file_type _ "','" _ checkname _ "');",source.chk_title) %]
+    [%- ELSE %]
+      [% L.button_tag("kivi.File.delete("   _ object_id _ ",'" _ object_type _ "','" _ file_type _ "','" _ checkname _ "'," _ is_global _ ");",  source.chk_title) %]
+    [%- END %]
+  [%- END %]
+  [%- IF source.can_rename %]
+    [%- can_rename = 1 %]
+    [% L.button_tag("kivi.File.rename(" _ object_id _ ",'" _ object_type _ "','" _ file_type _ "','" _ checkname _ "'," _ is_global _ ");",  source.rename_title ) %]
+  [%- END %]
+  [%- IF source.can_upload %]
+    [% L.button_tag("kivi.File.upload(" _ object_id _ ",'" _ object_type _ "','" _ file_type _ "','" _ source.upload_title _ "'," _ is_global _ ");", source.upload_title ) %]
+  [%- END %]
+  [%- IF source.can_import %]
+    [% L.button_tag("kivi.File.import("   _ object_id _ ",'" _ object_type _ "','" _ file_type _ "','" _ source.name _ "','" _ source.path _"');",  source.import_title ) %]
+  [%- END %]
+  </div>
+[%- END %]
+  <div></div><div>
+[% L.button_tag("kivi.File.update(" _ object_id _ ",'" _ object_type _ "','" _ file_type _ "'," _ is_global _ ");", LxERP.t8('Update')) %]
+  </div>
+</div>
+[%- IF ! json %]
+</div>
+[%- UNLESS is_global %]
+[%- IF can_rename %]
+[% INCLUDE 'file/rename_dialog.html' -%]
+[%- END %]
+[%- END %]
+[%- END %]
diff --git a/templates/webpages/file/upload_dialog.html b/templates/webpages/file/upload_dialog.html
new file mode 100644 (file)
index 0000000..eceb6b1
--- /dev/null
@@ -0,0 +1,23 @@
+[%- USE L -%][%- USE LxERP -%][%- USE JavaScript -%]
+
+<form method="post" id="upload_form" enctype="multipart/form-data" action="controller.pl">
+ <table>
+  <tr>
+   <td>[%- LxERP.t8("Filename") %]:</td><td>
+   <input type="file" name="uploadfiles[]" multiple="true" id="upload_files" size="45" accept="[% SELF.accept_types %]" onchange="kivi.File.allow_upload_submit();"></td>
+  </tr>
+ </table>
+
+ <p>
+  <input value="[%- LxERP.t8("Upload file") %]" id="upload_selected_button"
+         onclick="kivi.File.upload_selected_files([% SELF.object_id %],'[% SELF.object_type %]','[% SELF.file_type %]',[% SELF.maxsize %],[% SELF.is_global %]);"
+         type="button" disabled >
+  <a href="#" onclick="kivi.File.reset_upload_form();">[%- LxERP.t8("Reset") %]</a>
+  <a href="#" onclick="$('#files_upload').dialog('close');">[% LxERP.t8("Cancel") %]</a>
+ </p>
+
+ <hr>
+
+ <div id="upload_result"><p>&nbsp;</p></div>
+
+</form>
index 58af998..0d7ab08 100644 (file)
     [%- IF SELF.part.is_assembly %]
     <li><a href="#assembly_tab">[% 'Assembly items' | $T8 %]</a></li>
     [%- END %]
-[%- IF INSTANCE_CONF.get_doc_storage %]
+    [%- IF SELF.part.id %]
+    [%- IF INSTANCE_CONF.get_doc_storage %]
     <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=part&object_id=[% SELF.part.id %]">[% 'Attachments' | $T8 %]</a></li>
     <li><a href="controller.pl?action=File/list&file_type=image&object_type=part&object_id=[% SELF.part.id %]">[% 'Images' | $T8 %]</a></li>
-[%- END %]
+    [%- END %]
+    [%- END %]
     [% IF SELF.all_languages.size %]
     <li><a href="#translations_tab">[% 'Translations' | $T8 %]</a></li>
     [% END %]