From 2bb452ee62e74c2a42113171eeb53de8d09bbbf5 Mon Sep 17 00:00:00 2001 From: "Martin Helmling mh@waldpark.octosoft.eu" Date: Thu, 12 Dec 2013 17:19:19 +0100 Subject: [PATCH] Dateimanagement: Basiserweiterung MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit In diesem Commit sind die Anpassungen in der Mandantenkonfiguration sowie die notwendigen Klassen und Controller. Über eine Zwischenschicht wird das tatsächliche Backend (Dateien,WebDAV,ext.DMS,Datenbank etc) verborgen. --- SL/Controller/ClientConfig.pm | 2 +- SL/DB/File.pm | 48 ++ SL/DB/Helper/ALL.pm | 1 + SL/DB/Helper/Mappings.pm | 1 + SL/DB/Manager/File.pm | 15 + SL/DB/MetaSetup/Default.pm | 10 + SL/DB/MetaSetup/File.pm | 32 + SL/Dev/File.pm | 103 +++ SL/File.pm | 707 ++++++++++++++++++ SL/File/Backend.pm | 203 +++++ SL/File/Object.pm | 281 +++++++ locale/de/all | 68 +- sql/Pg-upgrade2/filemanagement_feature.sql | 13 + sql/Pg-upgrade2/files.sql | 27 + .../webpages/client_config/_attachments.html | 30 + .../webpages/client_config/_features.html | 63 +- templates/webpages/client_config/form.html | 6 + templates/webpages/common/search_history.html | 11 +- templates/webpages/file/rename_dialog.html | 18 + 19 files changed, 1613 insertions(+), 26 deletions(-) create mode 100644 SL/DB/File.pm create mode 100644 SL/DB/Manager/File.pm create mode 100644 SL/DB/MetaSetup/File.pm create mode 100644 SL/Dev/File.pm create mode 100644 SL/File.pm create mode 100644 SL/File/Backend.pm create mode 100644 SL/File/Object.pm mode change 100755 => 100644 locale/de/all create mode 100644 sql/Pg-upgrade2/filemanagement_feature.sql create mode 100644 sql/Pg-upgrade2/files.sql create mode 100644 templates/webpages/client_config/_attachments.html create mode 100644 templates/webpages/file/rename_dialog.html diff --git a/SL/Controller/ClientConfig.pm b/SL/Controller/ClientConfig.pm index c8afe1da0..a222a39f1 100644 --- a/SL/Controller/ClientConfig.pm +++ b/SL/Controller/ClientConfig.pm @@ -212,7 +212,7 @@ sub check_auth { sub edit_form { my ($self) = @_; - $::request->layout->use_javascript("${_}.js") for qw(jquery.selectboxes jquery.multiselect2side); + $::request->layout->use_javascript("${_}.js") for qw(jquery.selectboxes jquery.multiselect2side kivi.File); $self->render('client_config/form', title => t8('Client Configuration'), make_chart_title => sub { $_[0]->accno . '--' . $_[0]->description }, diff --git a/SL/DB/File.pm b/SL/DB/File.pm new file mode 100644 index 000000000..d44f07225 --- /dev/null +++ b/SL/DB/File.pm @@ -0,0 +1,48 @@ +# This file has been auto-generated only because it didn't exist. +# Feel free to modify it at will; it will not be overwritten automatically. + +package SL::DB::File; + +use strict; + +use SL::DB::MetaSetup::File; +use SL::DB::Manager::File; + +__PACKAGE__->meta->initialize; + + +1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + + SL::DB::File - Databaseclass for File + +=head1 SYNOPSIS + + use SL::DB::File; + + # synopsis... + +=head1 DESCRIPTION + + # longer description. + + +=head1 INTERFACE + + +=head1 DEPENDENCIES + + +=head1 SEE ALSO + +=head1 AUTHOR + + Werner Hahn Ewh@futureworldsearch.netE + +=cut diff --git a/SL/DB/Helper/ALL.pm b/SL/DB/Helper/ALL.pm index 2f747b9db..f1059f44c 100644 --- a/SL/DB/Helper/ALL.pm +++ b/SL/DB/Helper/ALL.pm @@ -51,6 +51,7 @@ use SL::DB::EmailJournal; use SL::DB::EmailJournalAttachment; use SL::DB::Employee; use SL::DB::Exchangerate; +use SL::DB::File; use SL::DB::Finanzamt; use SL::DB::FollowUp; use SL::DB::FollowUpAccess; diff --git a/SL/DB/Helper/Mappings.pm b/SL/DB/Helper/Mappings.pm index 0a20e1260..5718d3eb9 100644 --- a/SL/DB/Helper/Mappings.pm +++ b/SL/DB/Helper/Mappings.pm @@ -135,6 +135,7 @@ my %kivitendo_package_names = ( email_journal_attachments => 'EmailJournalAttachment', employee => 'employee', exchangerate => 'exchangerate', + files => 'file', finanzamt => 'finanzamt', follow_up_access => 'follow_up_access', follow_up_links => 'follow_up_link', diff --git a/SL/DB/Manager/File.pm b/SL/DB/Manager/File.pm new file mode 100644 index 000000000..3f6a06c95 --- /dev/null +++ b/SL/DB/Manager/File.pm @@ -0,0 +1,15 @@ +# This file has been auto-generated only because it didn't exist. +# Feel free to modify it at will; it will not be overwritten automatically. + +package SL::DB::Manager::File; + +use strict; + +use parent qw(SL::DB::Helper::Manager); +use SL::DB::Helper::Sorted; + +sub object_class { 'SL::DB::File' } + +__PACKAGE__->make_manager_methods; + +1; diff --git a/SL/DB/MetaSetup/Default.pm b/SL/DB/MetaSetup/Default.pm index 640ef1d7b..9d6114a66 100644 --- a/SL/DB/MetaSetup/Default.pm +++ b/SL/DB/MetaSetup/Default.pm @@ -45,6 +45,16 @@ __PACKAGE__->meta->columns( datev_check_on_purchase_invoice => { type => 'boolean', default => 'true' }, datev_check_on_sales_invoice => { type => 'boolean', default => 'true' }, disabled_price_sources => { type => 'array' }, + doc_database => { type => 'boolean', default => 'false' }, + doc_delete_printfiles => { type => 'boolean', default => 'false' }, + doc_files => { type => 'boolean', default => 'false' }, + doc_files_rootpath => { type => 'text', default => '' }, + doc_max_filesize => { type => 'integer', default => 1000000 }, + doc_storage => { type => 'boolean', default => 'false' }, + doc_storage_for_attachments => { type => 'text', default => 'Filesystem' }, + doc_storage_for_documents => { type => 'text', default => 'Filesystem' }, + doc_storage_for_images => { type => 'text', default => 'Filesystem' }, + doc_webdav => { type => 'boolean', default => 'false' }, dunning_ar => { type => 'integer' }, dunning_ar_amount_fee => { type => 'integer' }, dunning_ar_amount_interest => { type => 'integer' }, diff --git a/SL/DB/MetaSetup/File.pm b/SL/DB/MetaSetup/File.pm new file mode 100644 index 000000000..257e09faf --- /dev/null +++ b/SL/DB/MetaSetup/File.pm @@ -0,0 +1,32 @@ +# This file has been auto-generated. Do not modify it; it will be overwritten +# by rose_auto_create_model.pl automatically. +package SL::DB::File; + +use strict; + +use parent qw(SL::DB::Object); + +__PACKAGE__->meta->table('files'); + +__PACKAGE__->meta->columns( + backend => { type => 'text' }, + backend_data => { type => 'text' }, + description => { type => 'text' }, + file_name => { type => 'text', not_null => 1 }, + file_type => { type => 'text', not_null => 1 }, + id => { type => 'serial', not_null => 1 }, + itime => { type => 'timestamp', default => 'now()' }, + mime_type => { type => 'text', not_null => 1 }, + mtime => { type => 'timestamp' }, + object_id => { type => 'integer', not_null => 1 }, + object_type => { type => 'text', not_null => 1 }, + source => { type => 'text', not_null => 1 }, + title => { type => 'varchar', length => 45 }, +); + +__PACKAGE__->meta->primary_key_columns([ 'id' ]); + +__PACKAGE__->meta->allow_inline_column_values(1); + +1; +; diff --git a/SL/Dev/File.pm b/SL/Dev/File.pm new file mode 100644 index 000000000..ff1ea116d --- /dev/null +++ b/SL/Dev/File.pm @@ -0,0 +1,103 @@ +package SL::Dev::File; + +use strict; +use base qw(Exporter); +our @EXPORT = qw(create_scanned create_uploaded create_created get_all_count get_all get_all_versions delete_all); + +use SL::DB::File; + +sub create_scanned { + my (%params) = @_; + $params{source} = 'scanner1'; + $params{file_type} = 'document'; + $params{file_path} = '/var/tmp/'.$params{file_name} if !$params{file_path}; + open(OUT,"> ".$params{file_path}); + print OUT $params{file_contents}; + close(OUT); + delete $params{file_contents}; + my $file = _create_file(%params); + unlink($params{file_path}); + return $file; +} + +sub create_uploaded { + my (%params) = @_; + $params{source} = 'uploaded'; + $params{file_type} = 'attachment'; + return _create_file(%params); +} + +sub create_created { + my (%params) = @_; + $params{source} = 'created'; + $params{file_type} = 'document'; + return _create_file(%params); +} + +sub _create_file { + my (%params) = @_; + + my $fileobj = SL::File->save( + object_id => 1, + object_type => 'sales_order', + mime_type => 'text/plain', + description => 'Test File', + file_type => $params{file_type}, + source => $params{source}, + file_name => $params{file_name}, + file_contents => $params{file_contents}, + file_path => $params{file_path} + ); + return $fileobj; +} + +sub get_all_count { + my ($class,%params) = @_; + $params{object_id} = 1; + $params{object_type} = 'sales_order'; + return SL::File->get_all_count(%params); +} + +sub get_all { + my ($class,%params) = @_; + $params{object_id} = 1; + $params{object_type} = 'sales_order'; + SL::File->get_all(%params); +} + +sub get_all_versions { + my ($class,%params) = @_; + $params{object_id} = 1; + $params{object_type} = 'sales_order'; + SL::File->get_all_versions(%params); +} + +sub delete_all { + my ($class,%params) = @_; + $params{object_id} = 1; + $params{object_type} = 'sales_order'; + SL::File->delete_all(%params); +} +1; + +__END__ + +=head1 NAME + +SL::Dev::File - create file objects for testing, with minimal defaults + +=head1 FUNCTIONS + +=head2 C + +=head2 C + +=head2 C + +=head2 C + +=head1 AUTHOR + +Martin Helmling Emartin.helmling@opendynamic.deE + +=cut diff --git a/SL/File.pm b/SL/File.pm new file mode 100644 index 000000000..231e0622f --- /dev/null +++ b/SL/File.pm @@ -0,0 +1,707 @@ +package SL::File; + +use strict; + +use parent qw(Rose::Object); + +use Clone qw(clone); +use SL::File::Backend; +use SL::File::Object; +use SL::DB::History; +use SL::DB::File; +use SL::Helper::UserPreferences; +use SL::JSON; + +use constant RENAME_OK => 0; +use constant RENAME_EXISTS => 1; +use constant RENAME_NOFILE => 2; +use constant RENAME_SAME => 3; +use constant RENAME_NEW_VERSION => 4; + +sub get { + my ($self, %params) = @_; + die 'no id' unless $params{id}; + my $dbfile = SL::DB::Manager::File->get_first(query => [id => $params{id}]); + die 'not found' unless $dbfile; + $main::lxdebug->message(LXDebug->DEBUG2(), "object_id=".$dbfile->object_id." object_type=".$dbfile->object_type." dbfile=".$dbfile); + SL::File::Object->new(db_file => $dbfile, id => $dbfile->id, loaded => 1); +} + +sub get_version_count { + my ($self, %params) = @_; + die "no id or dbfile" unless $params{id} || $params{dbfile}; + $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile}; + die 'not found' unless $params{dbfile}; + my $backend = $self->_get_backend($params{dbfile}->backend); + return $backend->get_version_count(%params); +} + +sub get_all { + my ($self, %params) = @_; + + my @files; + return @files unless $params{object_type}; + return @files unless defined($params{object_id}); + + my @query = ( + object_id => $params{object_id}, + object_type => $params{object_type} + ); + push @query, (file_name => $params{file_name}) if $params{file_name}; + push @query, (file_type => $params{file_type}) if $params{file_type}; + push @query, (mime_type => $params{mime_type}) if $params{mime_type}; + push @query, (source => $params{source}) if $params{source}; + + my $sortby = $params{sort_by} || 'itime DESC,file_name ASC'; + + @files = @{ SL::DB::Manager::File->get_all(query => [@query], sort_by => $sortby) }; + map { SL::File::Object->new(db_file => $_, id => $_->id, loaded => 1) } @files; +} + +sub get_all_versions { + my ($self, %params) = @_; + my @versionobjs; + my @fileobjs = $self->get_all(%params); + if ( $params{dbfile} ) { + push @fileobjs, SL::File::Object->new(dbfile => $params{db_file}, id => $params{dbfile}->id, loaded => 1); + } else { + @fileobjs = $self->get_all(%params); + } + foreach my $fileobj (@fileobjs) { + $main::lxdebug->message(LXDebug->DEBUG2(), "obj=" . $fileobj . " id=" . $fileobj->id." versions=".$fileobj->version_count); + my $maxversion = $fileobj->version_count; + push @versionobjs, $fileobj; + if ($maxversion > 1) { + for my $version (2..$maxversion) { + $main::lxdebug->message(LXDebug->DEBUG2(), "clone for version=".($maxversion-$version+1)); + eval { + my $clone = clone($fileobj); + $clone->version($maxversion-$version+1); + $clone->newest(0); + $main::lxdebug->message(LXDebug->DEBUG2(), "clone version=".$clone->version." mtime=". $clone->mtime); + push @versionobjs, $clone; + 1; + } + } + } + } + return @versionobjs; +} + +sub get_all_count { + my ($self, %params) = @_; + return 0 unless $params{object_type}; + + my @query = ( + object_id => $params{object_id}, + object_type => $params{object_type} + ); + push @query, (file_name => $params{file_name}) if $params{file_name}; + push @query, (file_type => $params{file_type}) if $params{file_type}; + push @query, (mime_type => $params{mime_type}) if $params{mime_type}; + push @query, (source => $params{source}) if $params{source}; + + my $cnt = SL::DB::Manager::File->get_all_count(query => [@query]); + return $cnt; +} + +sub delete_all { + my ($self, %params) = @_; + return 0 unless defined($params{object_id}) || $params{object_type}; + my $files = SL::DB::Manager::File->get_all( + query => [ + object_id => $params{object_id}, + object_type => $params{object_type} + ] + ); + foreach my $file (@{$files}) { + $params{dbfile} = $file; + $self->delete(%params); + } +} + +sub delete { + my ($self, %params) = @_; + die "no id or dbfile" unless $params{id} || $params{dbfile}; + my $rc = SL::DB->client->with_transaction(\&_delete, $self, %params); + if (!$rc) { + my $err = SL::DB->client->error; + die (ref $err?$$err:$err); + } + return $rc; +} + +sub _delete { + my ($self, %params) = @_; + $params{dbfile} = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$params{dbfile}; + + my $backend = $self->_get_backend($params{dbfile}->backend); + if ( $params{dbfile}->file_type eq 'document' && $params{dbfile}->source ne 'created') + { + ## must unimport + my $hist = SL::DB::Manager::History->get_first( + where => [ + addition => 'IMPORT', + trans_id => $params{dbfile}->object_id, + what_done => $params{dbfile}->id + ] + ); + + if ($hist) { + if (!$main::auth->assert('import_ar | import_ap', 1)) { + die \'no permission to unimport'; + } + my $file = $backend->get_filepath(dbfile => $params{dbfile}); + $main::lxdebug->message(LXDebug->DEBUG2(), "del file=" . $file . " to=" . $hist->snumbers); + File::Copy::copy($file, $hist->snumbers) if $file; + $hist->addition('UNIMPORT'); + $hist->save; + } + } + if ($backend->delete(%params)) { + my $do_delete = 0; + if ( $params{last} || $params{all_but_notlast} ) { + if ( $backend->get_version_count > 0 ) { + $params{dbfile}->mtime(DateTime->now_local); + $params{dbfile}->save; + } else { + $do_delete = 1; + } + } else { + $do_delete = 1; + } + $params{dbfile}->delete if $do_delete; + return 1; + } + return 0; +} + +sub save { + my ($self, %params) = @_; + + my $obj = SL::DB->client->with_transaction(\&_save, $self, %params); + if (!$obj) { + my $err = SL::DB->client->error; + die (ref $err?$$err:$err); + } + return $obj; +} + +sub _save { + my ($self, %params) = @_; + my $file = $params{dbfile}; + my $exists = 0; + + if ($params{id}) { + $file = SL::DB::File->new(id => $params{id})->load; + die \'dbfile not exists' unless $file; + } elsif (!$file) { + $main::lxdebug->message(LXDebug->DEBUG2(), "obj_id=" .$params{object_id}); + die \'no object type set' unless $params{object_type}; + die \'no object id set' unless defined($params{object_id}); + + $exists = $self->get_all_count(%params); + die 'filename still exist' if $exists && $params{fail_if_exists}; + if ($exists) { + my ($obj1) = $self->get_all(%params); + $file = $obj1->db_file; + } else { + $file = SL::DB::File->new(); + $file->assign_attributes( + object_id => $params{object_id}, + object_type => $params{object_type}, + source => $params{source}, + file_type => $params{file_type}, + file_name => $params{file_name}, + mime_type => $params{mime_type}, + title => $params{title}, + description => $params{description}, + ); + } + } else { + $exists = 1; + } + if ($exists) { + #change attr on existing file + $file->file_name ($params{file_name}) if $params{file_name}; + $file->mime_type ($params{mime_type}) if $params{mime_type}; + $file->title ($params{title}) if $params{title}; + $file->description($params{description}) if $params{description}; + } + if ( !$file->backend ) { + $file->backend($self->_get_backend_by_file_type($file)); + # load itime for new file + $file->save->load; + } + $main::lxdebug->message(LXDebug->DEBUG2(), "backend3=" .$file->backend); + my $backend = $self->_get_backend($file->backend); + $params{dbfile} = $file; + $backend->save(%params); + + $file->mtime(DateTime->now_local); + $file->save; + if ($params{file_type} eq 'document' && $params{source} ne 'created') { + SL::DB::History->new( + addition => 'IMPORT', + trans_id => $params{object_id}, + snumbers => $params{file_path}, + employee_id => SL::DB::Manager::Employee->current->id, + what_done => $params{dbfile}->id + )->save(); + } + return $params{obj} if $params{dbfile} && $params{obj}; + return SL::File::Object->new(db_file => $file, id => $file->id, loaded => 1); +} + +sub rename { + my ($self, %params) = @_; + return RENAME_NOFILE unless $params{id} || $params{dbfile}; + my $file = $params{dbfile}; + $file = SL::DB::Manager::File->get_first(query => [id => $params{id}]) if !$file; + return RENAME_NOFILE unless $file; + + $main::lxdebug->message(LXDebug->DEBUG2(), "rename id=" . $file->id . " to=" . $params{to}); + if ($params{to}) { + return RENAME_SAME if $params{to} eq $file->file_name; + return RENAME_EXISTS if $self->get_all_count( 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 => $params{to} + ) > 0; + + my $backend = $self->_get_backend($file->backend); + $backend->rename(dbfile => $file) if $backend; + $file->file_name($params{to}); + $file->save; + } + return RENAME_OK; +} + +sub get_backend_class { + my ($self, $backendname) = @_; + die "no backend name set" unless $backendname; + $self->_get_backend($backendname); +} + +sub get_other_sources { + my ($self) = @_; + my $pref = SL::Helper::UserPreferences->new(namespace => 'file_sources'); + $pref->login("#default#"); + my @sources; + foreach my $tuple (@{ $pref->get_all() }) { + my %lkeys = %{ SL::JSON::from_json($tuple->{value}) }; + my $source = { + 'name' => $tuple->{key}, + 'description' => $lkeys{desc}, + 'directory' => $lkeys{dir} + }; + push @sources, $source; + } + return @sources; +} + +sub sync_from_backend { + my ($self, %params) = @_; + return unless $params{file_type}; + my $file = SL::DB::File->new; + $file->file_type($params{file_type}); + my $backend = $self->_get_backend(dbfile => $file->backend); + return unless $backend; + $backend->sync_from_backend(%params); +} + +# +# internal +# +sub _get_backend { + my ($self, $backend_name) = @_; + my $class = 'SL::File::Backend::' . $backend_name; + my $obj = undef; + eval { + eval "require $class"; + $obj = $class->new; + die \'backend not enabled' unless $obj->enabled; + 1; + } or do { + die \'backend class not found'; + }; + return $obj; +} + +sub _get_backend_by_file_type { + my ($self, $dbfile) = @_; + + $main::lxdebug->message(LXDebug->DEBUG2(), "_get_backend_by_file_type=" .$dbfile." type=".$dbfile->file_type); + return "Filesystem" unless $dbfile; + return $::instance_conf->get_doc_storage_for_documents if $dbfile->file_type eq 'document'; + return $::instance_conf->get_doc_storage_for_attachments if $dbfile->file_type eq 'attachment'; + return $::instance_conf->get_doc_storage_for_images if $dbfile->file_type eq 'image'; + return "Filesystem"; +} + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::File - The intermediate Layer for handling files + +=head1 SYNOPSIS + + # In a controller or helper ( see SL::Controller::File or SL::Helper::File ) + # you can create, remove, delete etc. a file in a backend independant way + + my $file = SL::File->save( + object_id => $self->object_id, + object_type => $self->object_type, + mime_type => 'application/pdf', + file_type => 'documents', + file_contents => 'this is no pdf'); + + my $file1 = SL::File->get(id => $id); + SL::File->delete(id => $id); + SL::File->delete(dbfile => $file1); + SL::File->delete_all(object_id => $object_id, + object_type => $object_type, + file_type => $filetype # may be optional + ); + SL::File->rename(id => $id,to => $newname); + my $files1 = SL::File->get_all(object_id => $object_id, + object_type => $object_type, + file_type => 'images', # may be optional + source => 'uploaded' # may be optional + ); + + # Alternativelly some operation can be done with the filemangement object wrapper + # and additional oparations see L + +=head1 OVERVIEW + +The Filemanagemt can handle files in a storage independant way. Internal the File +use the configured storage backend for the type of file. +These backends must be configured in L or an extra database table. + +There are three types of files: + +=over 2 + +=item - documents, + +which can be generated files (for sales), scanned files or uploaded files (for purchase) for an ERP-object. +They can exist in different versions. The versioning is handled implicit. All versions of a file may be +deleted by the user if she/he is allowed to do this. + +=item - attachments, + +which have additional information for an ERP-objects. They are uploadable. If a filename still exists +on a ERP-Object the new uploaded file is a new version of this or it must be renamed by user. + +There are generic attachments for a specific document group (like sales_invoices). This attachments can be +combinide/merged with the document-file in the time of printing. +Today only PDF-Attachmnets can be merged with the generated document-PDF. + +=item - images, + +they are like attachments, but they may be have thumbnails for displaying. +So the must have an image format like png,jpg. The versioning is like attachments + +=back + +For each type of files the backend can configured in L. + +The files have also the parameter C: + +=over 2 + +=item - created, generated by LaTeX + +=item - uploaded + +=item - scanner, import from scanner + +( or scanner1, scanner2 if there are different scanner, be configurable via UserPreferences ) + +=item - email, received by email and imported by hand or automatic. + +=back + +The files from source 'scanner' or 'email' are not allowed to delete else they must be send back to the sources. +This means they are moved back into the correspondent source directories. + +The scanner and email import must be configured via Table UserPreferences: + +=begin text + + id | login | namespace | version | key | value +----+---------+--------------+---------+----------+------------------------------------------------------ + 1 | default | file_sources | 0.00000 | scanner1 | {"dir":"/var/tmp/scanner1","desc":"Scanner Einkauf" } + 2 | default | file_sources | 0.00000 | emails | {"dir":"/var/tmp/emails" ,"desc":"Empfangene Mails"} + +=end text + +. + +The Fileinformation is stored in the table L for saving the information. +The modul and object_id describe the link to the object. + +The interface SL::File:Object encapsulate SL::DB:File, see L + +The storage backends are extra classes which depends from L. +So additional backend classes can be added. + +The implementation of versioning is done in the different backends. + +=head1 METHODS + +=over 4 + +=item C + +Creates a new SL::DB:File object or save an existing object for a specific backend depends of the C +and config, like + +=begin text + + SL::File->save( + object_id => $self->object_id, + object_type => $self->object_type, + content_type => 'application/pdf' + ); + +=end text + +. + +The file data is stored in the backend. If the file_type is "document" and the source is not "created" the file is imported, +so in the history the import is documented also as a hint to can unimport the file later. + +Available C: + +=over 4 + +=item C + +The id of SL::DB::File for an existing file + +=item C + +The Id of the ERP-object for a new file. + +=item C + +The Type of the ERP-object like "sales_quotation" for a new file. A clear mapping to the class/model exists in the controller. + +=item C + +The type may be "documents", "attachments" or "images" for a new file. + +=item C + +The type may be "created", "uploaded" or email sources or scanner sources for a new file. + +=item C + +The file_name of the file for a new file. This name is used in the WebGUI and as name for download. + +=item C + +The mime_type of a new file. This is used for downloading or for email attachments. + +=item C or C + +The description or title of a new file. This must be discussed if this attribute is needed. + +=back + +=item C<delete PARAMS> + +The file data is deleted in the backend. If the file comes from source 'scanner' or 'email' +they moved back to the source folders. This is documented in the history. + +Available C<PARAMS>: + +=over 4 + +=item C<id> + +The id of SL::DB::File + +=item C<dbfile> + +As alternative if the SL::DB::File as object is available. + +=back + +=item C<delete_all PARAMS> + +All file data of an ERP-object is deleted in the backend. + +=over 4 + +=item C<object_id> + +The Id of the ERP-object. + +=item C<object_type> + +The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller. + +=back + +=item C<rename PARAMS> + +The Filename of the file is changed + +Available C<PARAMS>: + +=over 4 + +=item C<id> + +The id of SL::DB::File + +=item C<to> + +The new filename + +=back + +=item C<get PARAMS> + +The actual file object is retrieved. The id of the object is needed. + +Available C<PARAMS>: + +=over 4 + +=item C<id> + +The id of SL::DB::File + +=back + +=item C<get_all PARAMS> + +All last versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved. + +Available C<PARAMS>: + +=over 4 + +=item C<object_id> + +The Id of the ERP-object. + +=item C<object_type> + +The Type of the ERP-object like "sales_quotation". A clear mapping to the class/model exists in the controller. + +=item C<file_type> + +The type may be "documents", "attachments" or "images". This parameter is optional. + +=item C<file_name> + +The name of the file . This parameter is optional. + +=item C<mime_type> + +The MIME type of the file . This parameter is optional. + +=item C<source> + +The type may be "created", "uploaded" or email sources or scanner soureces. This parameter is optional. + +=item C<sort_by> + +An optional parameter in which sorting the files are retrieved. Default is decrementing itime and ascending filename + +=back + +=item C<get_all_versions PARAMS> + +All versions of file data objects which are related to an ERP-Document,Part,Customer,Vendor,... are retrieved. +If only the versions of one file are wanted, additional parameter like file_name must be set. +If the param C<dbfile> set, only the versions of this file are returned. + +Available C<PARAMS> ar the same as L<get_all> + + + +=item C<get_all_count PARAMS> + +The count of available files is returned. +Available C<PARAMS> ar the same as L<get_all> + + +=item C<get_content PARAMS> + +The data of a file can retrieved. A reference to the data is returned. + +Available C<PARAMS>: + +=over 4 + +=item C<id> + +The id of SL::DB::File + +=item C<dbfile> + +If no Id exists the object SL::DB::File as param. + +=back + +=item C<get_file_path PARAMS> + +Sometimes it is more useful to have a path to the file not the contents. If the backend has not stored the content as file +it is in the responsibility of the backend to create a tempory session file. + +Available C<PARAMS>: + +=over 4 + +=item C<id> + +The id of SL::DB::File + +=item C<dbfile> + +If no Id exists the object SL::DB::File as param. + +=back + +=item C<get_other_sources> + +A helpful method to get the sources for scanner and email from UserPreferences. This method is need from SL::Controller::File + +=item C<sync_from_backend> + +For Backends which may be changed outside of kivitendo a synchronization of the database is done. +This sync must be triggered by a periodical task. + +Needed C<PARAMS>: + +=over 4 + +=item C<file_type> + +The synchronization is done file_type by file_type. + +=back + +=back + +=head1 AUTHOR + +Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt> + +=cut + diff --git a/SL/File/Backend.pm b/SL/File/Backend.pm new file mode 100644 index 000000000..a2ffe7312 --- /dev/null +++ b/SL/File/Backend.pm @@ -0,0 +1,203 @@ +package SL::File::Backend; + +use strict; + +use parent qw(Rose::Object); + +sub store { die 'store needs to be implemented' } + +sub delete { die 'delete needs to be implemented' } + +sub rename { die 'rename needs to be implemented' } + +sub get_content { die 'get_content needs to be implemented' } + +sub get_filepath { die 'get_filepath needs to be implemented' } + +sub get_mtime { die 'get_mtime needs to be implemented' } + +sub get_version_count { die 'get_version_count needs to be implemented' } + +sub enabled { 0; } + +sub sync_from_backend { } + +1; + +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::File::Backend - Base class for file storage backend + +=head1 SYNOPSIS + +See the synopsis of L<SL::File> and L<SL::File::Object> + +=head1 OVERVIEW + +The most methods must be overridden by the specific storage backend + +See also the overview of L<SL::File> and L<SL::File::Object>. + + +=head1 METHODS + +=over 4 + +=item C<store PARAMS> + +The file data is stored in the backend. + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=item C<file_contents> + +The data of the file to store + +=item C<file_path> + +If the data is still in a file, the contents of this file is copied. + +=back + +If both parameter C<file_contents> and C<file_path> exists, +the backend is responsible in which way the contents is fetched. + +If the file exists the backend is responsible to decide to save a new version of the file or override the +latest existing file. + +=item C<delete PARAMS> + +The file data is deleted in the backend. + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=item C<last> + +If this parameter is set only the latest version of the file are deleted. + +=item C<all_but_notlast> + +If this parameter is set all versions of the file are deleted except the latest version. + +If none of the two parameters C<all_versions> or C<all__but_notlast> is set +all version of the file are deleted. + +=back + +=item C<rename PARAMS> + +The Filename of the file is changed. If the backend is not dependant from the filename +nothing must happens. The rename must work on all versions of the file. + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=back + +=item C<get_version_count PARAMS> + +The count of the available versions of a file will returned. +The versions are numbered from 1 up to the returned value + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +=back + +=item C<get_mtime PARAMS> + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=item C<version> + +The version number of the file for which the modification timestamp is wanted. +If no version set or version is 0 , the mtime of the latest version is returned. + +=back + +=item C<get_content PARAMS> + +For downloading or printing the real data need to retrieve. +A reference of the data must be returned. + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=back + +=item C<get_file_path PARAMS> + +If the backend has files as storage, the file path can returned. +If a file is not available in the backend a temporary file must be created with the contents. + +Available C<PARAMS>: + +=over 4 + +=item C<dbfile> + +The object SL::DB::File as param. + +=back + +=item C<enabled> + +returns 1 if the backend is enabled and has all config to work. +In other cases it must return 0 + +=item C<sync_from_backend> + +For Backends which may be changed outside of kivitendo a synchronization of the database is done. +Normally the backend is responsible to actualise the data if it needed. +This interface can be used if a long work must be done and runs in a extra task. + +=back + +=head1 SEE ALSO + +L<SL::File>, L<SL::File::Object> + +=head1 AUTHOR + +Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt> + +=cut + + diff --git a/SL/File/Object.pm b/SL/File/Object.pm new file mode 100644 index 000000000..fd1052861 --- /dev/null +++ b/SL/File/Object.pm @@ -0,0 +1,281 @@ +package SL::File::Object; + +use strict; +use parent qw(Rose::Object); +use DateTime; + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw() ], + 'scalar --get_set_init' => [ qw(db_file loaded id version newest) ], +); + +#use SL::DB::Helper::Attr; +#__PACKAGE__->_as_timestamp('mtime'); +# wie wird das mit dem Attr Helper gemacht damit er bei nicht DB Objekten auch geht? + +sub mtime_as_timestamp_s { + $::locale->format_date_object($_[0]->mtime, precision => 'second'); +} + +# wrapper methods + +sub itime { + $_[0]->loaded_db_file->itime; +} + +sub file_name { + $_[0]->loaded_db_file->file_name; +} + +sub file_type { + $_[0]->loaded_db_file->file_type; +} + +sub file_name { + $_[0]->loaded_db_file->file_name; +} + +sub object_type { + $_[0]->loaded_db_file->object_type; +} + +sub object_id { + $_[0]->loaded_db_file->object_id; +} + +sub mime_type { + $_[0]->loaded_db_file->mime_type; +} + +sub file_title { + $_[0]->loaded_db_file->title; +} + +sub file_description { + $_[0]->loaded_db_file->description; +} + +sub backend { + $_[0]->loaded_db_file->backend; +} + +sub source { + $_[0]->loaded_db_file->source; +} + +# methods to go directly into the backends + +sub get_file { + $_[0]->backend_class->get_filepath(dbfile => $_[0]->loaded_db_file, version => $_[0]->version) +} + +sub get_content { + $_[0]->backend_class->get_content(dbfile => $_[0]->loaded_db_file, version => $_[0]->version) +} + +sub mtime { + $_[0]->backend_class->get_mtime(dbfile => $_[0]->loaded_db_file, version => $_[0]->version) +} + +sub version_count { + $_[0]->backend_class->get_version_count(dbfile => $_[0]->loaded_db_file) +} + +sub versions { + SL::File->get_all_versions(dbfile => $_[0]->loaded_db_file) +} + +sub save_contents { + SL::File->save(dbfile => $_[0]->loaded_db_file, file_contents => $_[1] ) +} + +sub save_file { + SL::File->save(dbfile => $_[0]->loaded_db_file, file_path => $_[1] ) +} + +sub delete { + SL::File->delete(dbfile => $_[0]->loaded_db_file) +} + +sub delete_last_version { + SL::File->delete(dbfile => $_[0]->loaded_db_file, last => 1 ) +} + +sub purge { + SL::File->delete(dbfile => $_[0]->loaded_db_file, all_but_notlast => 1 ) +} + +sub rename { + SL::File->rename(dbfile => $_[0]->loaded_db_file, to => $_[1]) +} + +# internals + +sub backend_class { + SL::File->get_backend_class($_[0]->backend) +} + + +sub loaded_db_file { # so, dass wir die nur einmal laden. + if (!$_[0]->loaded) { + $_[0]->db_file->load; + $_[0]->loaded(1); + } + $_[0]->db_file; +} + + +sub init_db_file { die 'must always have a db file'; } +sub init_loaded { 0 } +sub init_id { 0 } +sub init_version { 0 } +sub init_newest { 1 } + +1; + +__END__ + +=encoding utf-8 + +=head1 NAME + +SL::File::Object - a filemangement object wrapper + +=head1 SYNOPSIS + + use SL::File; + + my ($object) = SL::File->get_all(object_id => $object_id, + object_type => $object_type, + file_type => 'images', # may be optional + source => 'uploaded' # may be optional + ); +# read attributes + + my $object_id = $object->object_id, + my $object_type = $object->object_type, + my $file_type = $object->file_type; + my $file_name = $object->file_name; + my $mime_type = $object->mime_type; + + my $mtime = $object->mtime; + my $itime = $object->itime; + my $id = $object->id; + my $newest = $object->newest; + + my $versions = $object->version_count; + + my @versionobjs = $object->versions; + foreach ( @versionobjs ) { + my $mtime = $_->mtime; + my $contents = $_->get_content; + } + +# update + + $object->rename("image1.png"); + $object->save_contents("new text"); + $object->save_file("/tmp/empty.png"); + $object->purge; + $object->delete_last_version; + $object->delete; + +=head1 DESCRIPTION + +This is a wrapper around a single object in the filemangement. + +=head1 METHODS + +Following methods are wrapper to read the attributes of L<SL::DB::File> : + +=over 4 + +=item C<object_id> + +=item C<object_type> + +=item C<file_type> + +=item C<file_name> + +=item C<mime_name> + +=item C<file_title> + +=item C<file_description> + +=item C<backend> + +=item C<source> + +=item C<itime> + +=item C<id> + +=back + +Additional are there special methods. If the Object is created by SL::File::get_all_versions() +or by "$object->versions" +it has a version number. So the different mtime, filepath or content can be retrieved: + +=over 4 + +=item C<mtime> + +get the modification time of a (versioned) object + +=item C<get_file> + +get the full qualified file path of the (versioned) object + +=item C<get_content> + +get the content of the (versioned) object + +=item C<version_count> + +Get the available versions of the file + +=item C<versions> + +Returns an array of SL::File::Object objects with the available versions of the file, starting with the newest version. + +=item C<newest> + +If set this is the newest version of the file. + +=item C<save_contents $contents> + +Store a new contents to the file (as a new version). + +=item C<save_file $filepath> + +Store the content of an (absolute)file path to the file + +=item C<delete> + +Delete the file with all of his versions + +=item C<delete_last_version> + +Delete only the last version of the file with implicit decrement of the version_count. + +=item C<purge> + +Delete all old versions of the file. Only one version exist after purge. The version count is reset to 1. + +=item C<rename $newfilename> + +Renames the filename + +=back + +=head1 SEE ALSO + +L<SL::File> + +=head1 AUTHOR + +Martin Helmling E<lt>martin.helmling@opendynamic.deE<gt> + +=cut diff --git a/locale/de/all b/locale/de/all old mode 100755 new mode 100644 index 6ab574684..052b63e8b --- a/locale/de/all +++ b/locale/de/all @@ -27,9 +27,9 @@ $self->{texts} = { '#1 text block(s) back' => '#1 Textlock/-blöcke vorne', '#1 text block(s) front' => '#1 Textblock/-blöcke hinten', '%' => '%', - '(recommended) Insert the used currencies in the system. You can simply change the name of the currencies by editing the textfields above. Do not use a name of a currency that is already in use.' => '(empfohlen) Fügen Sie die verwaisten Währungen in Ihr System ein. Sie können den Namen der Währung einfach ändern, indem Sie die Felder oben bearbeiten. Benutzen Sie keine Namen von Währungen, die Sie bereits benutzen.', + '(recommended) Insert the used currencies in the system. You can simply change the name of the currencies by editing the textfields above. Do not use a name of a currency that is already in use.' => '(empfohlen) Fügen Sie die verwaisten Währungen in Ihr System ein. Sie können den Namen der Währung einfach ändern, indem Sie die Felder oben bearbeiten. Benutzen Sie keine Namen von Währungen, die Sie bereits benutzen.', '*/' => '*/', - ', if set' => ', falls gesetzt', + ', if set' => '', '---please select---' => '---bitte auswählen---', '. Automatically generated.' => '. Automatisch erzeugt.', '...after logging in' => '...nach dem Anmelden', @@ -61,7 +61,7 @@ $self->{texts} = { 'A unit with this name does already exist.' => 'Eine Einheit mit diesem Namen existiert bereits.', 'A valid taxkey is missing!' => 'Ein gültiger Steuerschlüssel fehlt!', 'A variable marked as \'Deactivate by default\' isn\'t automatically added to all articles, and has to be explicitly added for each desired article in its master data tab. Only then can the variable be used for that article in the records.' => 'Eine als \'Deaktiviert als Voreinstellung\' markierte Variable wird nicht automatisch bei allen Artikeln hinzugefügt, sondern muß explizit für jeden gewünschten Artikel in den Stammdaten aktiviert werden. Erst danach ist die Variable für den Artikel in Belegen bearbeitbar.', - 'A variable marked as \'editable\' can be changed in each quotation, order, invoice etc.' => 'Eine als \'Bearbeitbar\' markierte Variable kann in jedem Angebot, Auftrag, jeder Rechnung etc für jede Position geändert werden.', + 'A variable marked as \'editable\' can be changed in each quotation, order, invoice etc.' => 'Eine als \'editierbar\' markierte Variable kann in jedem Angebot, Auftrag, jeder Rechnung etc für jede Position geändert werden.', 'ADDED' => 'Hinzugefügt', 'AP' => 'Einkauf', 'AP Aging' => 'Offene Verbindlichkeiten', @@ -238,7 +238,7 @@ $self->{texts} = { 'All partsgroups' => 'Alle Warengruppen', 'All price sources' => 'Alle Preisquellen', 'All reports' => 'Alle Berichte (Kontenübersicht, Summen- u. Saldenliste, Erfolgsrechnung, GuV, BWA, Bilanz, Projektbuchungen)', - 'All the other clients will start with an empty set of WebDAV folders.' => 'Alle anderen Mandanten werden mit einem leeren Satz von WebDAV-Ordnern ausgestattet.', + 'All the other clients will start with an empty set of WebDAV folders.' => 'Alle anderen Mandanten werden mit einem leeren Satz von Dokumenten-Ordnern ausgestattet.', 'All the selected exports have already been closed, or all of their items have already been executed.' => 'Alle ausgewählten Exporte sind als abgeschlossen markiert, oder für alle Einträge wurden bereits Zahlungen verbucht.', 'All transactions' => 'Alle Buchungen', 'All units have either no or exactly one base unit of which they are multiples.' => 'Einheiten haben entweder keine oder genau eine Basiseinheit, von der sie ein Vielfaches sind.', @@ -249,6 +249,7 @@ $self->{texts} = { 'Allow direct creation of new purchase delivery orders' => 'Direktes Anlegen neuer Einkaufslieferscheine zulassen', 'Allow direct creation of new purchase invoices' => 'Direktes Anlegen neuer Einkaufsrechnungen zulassen', 'Allow the following users access to my follow-ups:' => 'Erlaube den folgenden Benutzern Zugriff auf meine Wiedervorlagen:', + 'Allow to delete generated printfiles' => 'Löschen von erzeugten Dokumenten erlaubt', 'Always save orders with a projectnumber (create new projects)' => 'Aufträge immer mit Projektnummer speichern (neue Projekt erstellen)', 'Amended Advance Turnover Tax Return' => 'Berichtigte Anmeldung', 'Amount' => 'Betrag', @@ -268,7 +269,7 @@ $self->{texts} = { 'An upper-case character is required.' => 'Ein Großbuchstabe ist vorgeschrieben.', 'Annotations' => 'Anmerkungen', 'Any stock contents containing a best before date will be impossible to stock out otherwise.' => 'Sonst können Artikel, bei denen ein Mindesthaltbarkeitsdatum gesetzt ist, nicht mehr ausgelagert werden.', - 'Ap aging on %s' => 'Offene Verbindlichkeiten zum %s', + 'Ap aging on %s' => '', 'Application Error. No Format given' => 'Fehler in der Anwendung. Das Ausgabeformat fehlt.', 'Application Error. Wrong Format' => 'Fehler in der Anwendung. Falsches Format: ', 'Apply to all parts' => 'Bei allen Artikeln setzen', @@ -788,7 +789,7 @@ $self->{texts} = { 'Customer/Vendor' => 'Kunde/Lieferant', 'Customer/Vendor (database ID)' => 'Kunde/Lieferant (Datenbank-ID)', 'Customer/Vendor Name' => 'Kunde/Lieferant', - 'Customer/Vendor Number' => 'Kunden-/Lieferantennummer', + 'Customer/Vendor Number' => 'Kunden-/<br>Lieferantennummer', 'Customer/Vendor name' => 'Kunden-/Lieferantenname', 'Customer/Vendor number' => 'Kunden-/Lieferantennummer', 'Customer/Vendor/Remote name' => 'Kunden/Lieferantenname laut Bank', @@ -815,6 +816,7 @@ $self->{texts} = { 'Data type' => 'Datentyp', 'DataSet #1' => 'Datensatz #1', 'DataSet for GoBD version #1. Created with kivitendo #2 by #3 (#4)' => 'Datenüberlassung nach GoBD vom #1. Erstellt mit kivitendo #2. Ansprechpartner ist #3 (#4)', + 'Database' => '', 'Database Administration' => 'Datenbankadministration', 'Database Connection Test' => 'Test der Datenbankverbindung', 'Database Host' => 'Datenbankcomputer', @@ -892,6 +894,7 @@ $self->{texts} = { 'Delete drafts' => 'Entwürfe löschen', 'Delete links' => 'Verknüpfungen löschen', 'Delete picture' => 'Bild löschen', + 'Delete printfiles' => 'Dokumente löschen', 'Delete profile' => 'Profil löschen', 'Delete quotation/order' => 'Angebot/Auftrag löschen', 'Delete requirement spec' => 'Pflichtenheft löschen', @@ -1300,7 +1303,10 @@ $self->{texts} = { 'Fee' => 'Gebühr', 'Field' => 'Feld', 'File' => 'Datei', + 'File Management' => 'Dateimanagement', 'File name' => 'Dateiname', + 'Filemanagement' => 'Dateimanagement', + 'Files' => 'Dateien', 'Filter' => 'Filter', 'Filter by Partsgroups' => 'Nach Warengruppen filtern', 'Filter date by' => 'Datum filtern nach', @@ -1316,7 +1322,7 @@ $self->{texts} = { 'First 20 Lines' => 'Nur erste 20 Datensätze', 'Fix transaction' => 'Buchung korrigieren', 'Fix transactions' => 'Buchungen korrigieren', - 'Focus position after update' => 'Kursor-Position nach Erneuern', + 'Focus position after update' => '', 'Folgekonto' => 'Folgekonto', 'Follow-Up' => 'Wiedervorlage', 'Follow-Up Date' => 'Wiedervorlagedatum', @@ -1381,6 +1387,7 @@ $self->{texts} = { 'Germany' => 'Deutschland', 'Git revision: #1, #2 #3' => 'Git-Revision: #1, #2 #3', 'Given Name' => 'Vorname', + 'Global Attachments' => 'Allgemeine Dokumentenanhänge', 'Global Record BCC' => 'Globale BCC-Adresse', 'GoBD Export' => 'GoBD Export', 'Greeting' => 'Anrede', @@ -1432,6 +1439,7 @@ $self->{texts} = { 'ID/Acc_ID' => 'ID/Acc_ID', 'II' => 'II', 'III' => 'III', + 'IMPORT' => 'Importiert', 'IV' => 'IV', 'If all of the following match' => 'Wenn alle der folgenden Bedingungen zutreffen', 'If amounts differ more than "Maximal amount difference" (see settings), this item is marked as invalid.' => 'Weichen die Beträge mehr als die "maximale Betragsabweichung" (siehe Einstellungen) ab, so wird diese Position als ungültig markiert.', @@ -1584,7 +1592,7 @@ $self->{texts} = { 'Keep the project link the way it is.' => 'Die aktuelle Verknüpfung beibehalten.', 'Known Column' => 'Bekannte Spalte', 'Konten' => 'Konten', - 'L' => 'L', + 'L' => 'T', 'LIABILITIES' => 'PASSIVA', 'LP' => 'LP', 'LaTeX Templates' => 'LaTeX-Vorlagen', @@ -1996,7 +2004,7 @@ $self->{texts} = { 'Own bank account number or IBAN' => 'Eigene Kontonummer oder IBAN', 'Own bank code' => 'Eigene Bankleitzahl', 'Owner of account' => 'Kontoinhaber', - 'PAYMENT POSTED' => 'Zahlung gebucht', + 'PAYMENT POSTED' => 'Rechnung gebucht', 'PDF' => 'PDF', 'PDF (OpenDocument/OASIS)' => 'PDF (OpenDocument/OASIS)', 'PDF export -- options' => 'PDF-Export -- Optionen', @@ -2106,6 +2114,7 @@ $self->{texts} = { 'Please insert object dimensions below.' => 'Bitte geben Sie die Abmessungen unten ein', 'Please install the below listed modules or ask your system administrator to.' => 'Bitte installieren Sie die unten aufgeführten Module, oder bitten Sie Ihren Administrator darum.', 'Please log in to the administration panel.' => 'Bitte melden Sie sich im Administrationsbereich an.', + 'Please modify filename' => 'Bitte Dateinamen editieren', 'Please re-run the analysis for broken general ledger entries by clicking this button:' => 'Bitte wiederholen Sie die Analyse der Hauptbucheinträge, indem Sie auf diesen Button klicken:', 'Please read the file' => 'Bitte lesen Sie die Datei', 'Please select a customer from the list below.' => 'Bitte einen Endkunden aus der Liste auswählen', @@ -2242,6 +2251,7 @@ $self->{texts} = { 'Purpose' => 'Verwendungszweck', 'Purpose (if field names purpose, purpose1, purpose2 ... exist they will all combined into the field "purpose")' => 'Verwendungszweck (wenn die Spalten purpose, purpose1, purpose2 ... existieren werden diese zum Feld "purpose" zusammengefügt)', 'Purpose/Reference' => 'Verwendungszweck und Referenz', + 'QUEUED' => 'In Warteschlange', 'Qty' => 'Menge', 'Qty according to delivery order' => 'Menge laut Lieferschein', 'Qty equal or less than #1' => 'Menge gleich oder kleiner als #1', @@ -2310,7 +2320,7 @@ $self->{texts} = { 'Reduced Master Data' => 'Abschlag', 'Reference' => 'Referenz', 'Reference / Invoice Number' => 'Referenz / Rechnungsnummer', - 'Reference day' => 'Stichtag', + 'Reference day' => '', 'Reference missing!' => 'Referenz fehlt!', 'Release From Stock' => 'Lagerausgang', 'Remaining' => 'Rest', @@ -2391,6 +2401,7 @@ $self->{texts} = { 'Risk' => 'Risiko', 'Risk levels' => 'Risikograde', 'Risks' => 'Risikograde', + 'Root path for file storage' => 'Absoluter Pfad zu Dateisystem', 'Rounding' => 'Rundung', 'Rounding Gain' => 'Rundungserträge', 'Rounding Loss' => 'Rundungsaufwendungen', @@ -2409,7 +2420,7 @@ $self->{texts} = { 'Run task server for this client with the following user' => 'Task-Server für diesen Mandanten mit der folgenden BenutzerIn ausführen', 'Run tests' => 'Tests ausführen', 'SAVED' => 'Gespeichert', - 'SAVED FOR DUNNING' => 'Gespeichert', + 'SAVED FOR DUNNING' => 'Gespeichert zum Mahnen', 'SCREENED' => 'Angezeigt', 'SEPA' => 'SEPA', 'SEPA XML download' => 'SEPA-XML-Download', @@ -2480,7 +2491,7 @@ $self->{texts} = { 'Save and keep open' => 'Speichern und geöffnet lassen', 'Save as a new draft.' => 'Als neuen Entwurf speichern', 'Save as new' => 'als neu speichern', - 'Save document in WebDAV repository' => 'Dokument in WebDAV-Ablage speichern', + 'Save document in WebDAV repository' => 'Speichere Dokumente in WebDAV', 'Save draft' => 'Entwurf speichern', 'Save invoices' => 'Rechnungen speichern', 'Save profile' => 'Profil speichern', @@ -2672,9 +2683,9 @@ $self->{texts} = { 'Starting date' => 'Anfangsdatum', 'Starting the task server failed.' => 'Das Starten des Task-Servers schlug fehl.', 'Starting with version 2.6.3 the configuration files in "config" have been consolidated.' => 'Ab Version 2.6.3 wurden die Konfiguration vereinfacht und es gibt nur noch eine Konfigurationsdatei im Verzeichnis config', - 'Statement' => 'Sammelrechnung', + 'Statement' => 'Statement', 'Statement Balance' => 'Sammelrechnungsbilanz', - 'Statement sent to' => 'Sammelrechnung verschickt an', + 'Statement sent to' => '', 'Statements sent to printer!' => 'Sammelrechnungen an Drucker geschickt!', 'Status' => 'Status', 'Step 1 -- limit number of delivery orders to process' => 'Schritt 1 -- Anzahl zu verarbeitender Lieferscheine begrenzen', @@ -2688,6 +2699,10 @@ $self->{texts} = { 'Stocked Qty' => 'Lagermenge', 'Stop task server' => 'Task-Server beenden', 'Stopping the task server failed. Output:' => 'Das Beenden des Task-Servers schlug fehl.', + 'Storage Backends' => 'Datei-Speicher', + 'Storage Type for Attachments' => 'Speichertyp für Anhänge', + 'Storage Type for generated/imported PDF Documents' => 'Speichertyp für erzeugte oder importierte Dokumente', + 'Storage Type for images' => 'Speichertyp für Bilder', 'Storing PDF to webdav folder failed: #1' => 'Speichern der PDF im WebDAV Ordner fehlgeschlagen: #1', 'Storing the emails in the journal is currently disabled in the client configuration.' => 'Das Speichern von versendeten E-Mails ist derzeit in der Mandantenkonfigurierung abgeschaltet.', 'Storno' => 'Storno', @@ -2971,6 +2986,7 @@ $self->{texts} = { 'The login is not unique.' => 'Der Loginname ist nicht eindeutig.', 'The long description is missing.' => 'Der Langtext fehlt.', 'The master templates where not found.' => 'Der Vorlagensatz wurde nicht gefunden.', + 'The maximum of uploadable filesize' => 'Die maximale Dateigröße in Bytes die hochladbar ist', 'The name and description are not unique.' => 'Name und Beschreibung sind nicht einmalig.', 'The name in row %d has already been used before.' => 'Der Name in Zeile %d wurde vorher bereits benutzt.', 'The name is invalid.' => 'Der Name ist ungültigt.', @@ -3204,6 +3220,7 @@ $self->{texts} = { 'This is the client to be selected by default on the login screen.' => 'Dies ist derjenige Mandant, der im Loginbildschirm standardmäßig ausgewählt sein wird.', 'This is the default bin for parts' => 'Standard-Lagerplatz für Stammdaten/Waren', 'This is the default warehouse for ignoring onhand' => 'Standardlager für Auslagern ohne Prüfung auf Bestand.', + 'This is the root directory for the File storage backend, must be writable for webserver' => 'Dies ist das Wurzelverzeichnis für das Datei-Backend, es muss schreibbar für den Webserver sein.', 'This list is capped at 15 items to keep it fast. If you need a full list, please use reports.' => 'Diese Liste ist auf 15 Zeilen begrenzt. Wenn Sie eine vollständige Liste benötigen, erstellen Sie bitte einen Bericht.', 'This makemodel price does not exist anymore' => 'Dieser Lieferantenpreis existiert nicht mehr', 'This means that the user has created an AP transaction and chosen a taxkey for sales taxes, or that he has created an AR transaction and chosen a taxkey for input taxes.' => 'Das bedeutet, dass ein Benutzer eine Kreditorenbuchung angelegt und in ihr einen Umsatzsteuer-Steuerschlüssel verwendet oder eine Debitorenbuchung mit Vorsteuer-Steuerschlüssel angelegt hat.', @@ -3313,6 +3330,7 @@ $self->{texts} = { 'Type of Vendor' => 'Lieferantentyp', 'TypeAbbreviation' => 'Typ-Abkürzung', 'Types of Business' => 'Kunden-/Lieferantentypen', + 'UNIMPORT' => 'Import rückgängig', 'USTVA' => 'USTVA', 'USTVA 2004' => 'USTVA 2004', 'USTVA 2005' => 'USTVA 2005', @@ -3373,11 +3391,15 @@ $self->{texts} = { 'UsageWithout' => 'Entnommen (ohne Korr.)', 'Use As New' => 'Als neu verwenden', 'Use Balance Sheet' => 'Bilanz verwenden', + 'Use Database Storage backend (not implemented yet!)' => 'Verwende Datenbank-Backend (NICHT IMPLEMENTIERT !)', 'Use Datevautomatik' => 'Datev-Automatik verwenden', 'Use Erfolgsrechnung' => 'Erfolgsrechnung verwenden', + 'Use File Storage backend' => 'Verwende Dateisystem-Backend', + 'Use Filemanagement' => 'Verwende Dateimanagement', 'Use Income' => 'GUV und BWA verwenden', 'Use UStVA' => 'UStVA verwenden', - 'Use WebDAV Repository' => 'WebDAV-Ablage verwenden', + 'Use WebDAV Repository' => 'Verwende WebDAV', + 'Use WebDAV Storage backend' => 'Verwende WebDAV-Backend', 'Use as new' => 'Als neu verwenden', 'Use default booking group because setting is \'all\'' => 'Standardbuchungsgruppe wird verwendet', 'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe', @@ -3385,6 +3407,9 @@ $self->{texts} = { 'Use existing templates' => 'Vorhandene Druckvorlagen verwenden', 'Use linked items' => 'Verknüpfte Positionen verwenden', 'Use master default bin for Default Transfer, if no default bin for the part is configured' => 'Standardlagerplatz für Ein- / Auslagern über Standard-Lagerplatz, falls für die Ware kein expliziter Lagerplatz konfiguriert ist', + 'Use this storage backend for all generated PDF-Files' => 'Verwende dieses Backend für generierte PDF-Dateien', + 'Use this storage backend for all uploaded attachments' => 'Verwende dieses Backend für hochgeladene Dateien', + 'Use this storage backend for uploaded images' => 'Verwende dieses Backend für hochgeladene Bilder', 'Useable for…' => 'Benutzbar für…', 'Used for Purchase' => 'im Einkauf verwenden', 'Used for Sale' => 'im Verkauf verwenden', @@ -3622,12 +3647,14 @@ $self->{texts} = { 'every time' => 'immer', 'executed' => 'ausgeführt', 'execution as user \'#1\'' => 'Ausführung als User »#1«', + 'ext.DMS' => 'externes DMS', 'failed' => 'fehlgeschlagen', 'false' => 'falsch', 'female' => 'weiblich', 'flat-rate position' => 'Pauschalposition', 'follow_up_list' => 'wiedervorlageliste', 'for' => 'für', + 'for Document types' => 'für unterschiedliche ERP Dokumententypen', 'for Period' => 'für den Zeitraum', 'for all' => 'für alle', 'for date' => 'zum Stichtag', @@ -3639,9 +3666,9 @@ $self->{texts} = { 'gobd-#1-#2.zip' => 'gobd-#1-#2.zip', 'h' => 'h', 'history' => 'Historie', - 'history search engine' => 'Historien Suchmaschine', + 'history search engine' => '', 'inactive' => 'inaktiv', - 'income' => 'GUV und BWA', + 'income' => 'Einnahmen-Überschuß-Rechnung', 'invoice' => 'Rechnung', 'invoice mode or item mode' => 'Rechnungsmodus oder Artikelmodus', 'invoice_list' => 'debitorenbuchungsliste', @@ -3652,7 +3679,7 @@ $self->{texts} = { 'is greater than or equal' => 'ist größer oder gleich', 'is lower than or equal' => 'ist kleiner oder gleich', 'kivitendo' => 'kivitendo', - 'kivitendo Homepage' => 'Infos zu kivitendo', + 'kivitendo Homepage' => '', 'kivitendo can fix these problems automatically.' => 'kivitendo kann solche Probleme automatisch beheben.', 'kivitendo has been extended to handle multiple clients within a single installation.' => 'kivitendo wurde um Mandantenfähigkeit erweitert.', 'kivitendo has found one or more problems in the general ledger.' => 'kivitendo hat ein oder mehrere Probleme im Hauptbuch gefunden.', @@ -3660,9 +3687,9 @@ $self->{texts} = { 'kivitendo is now able to manage warehouses instead of just tracking the amount of goods in your system.' => 'kivitendo enthält jetzt auch echte Lagerverwaultung anstatt reiner Mengenzählung.', 'kivitendo modules' => 'Module', 'kivitendo needs to update the authentication database before you can proceed.' => 'kivitendo muss die Authentifizierungsdatenbank aktualisieren, bevor Sie fortfahren können.', - 'kivitendo v#1' => 'kivitendo v#1', + 'kivitendo v#1' => '', 'kivitendo v#1 administration' => 'kivitendo v#1 Administration', - 'kivitendo website (external)' => 'kivitendo-Webseite (extern)', + 'kivitendo website (external)' => '', 'kivitendo will then update the database automatically.' => 'kivitendo wird die Datenbank daraufhin automatisch aktualisieren.', 'letters_list' => 'briefliste', 'list_of_payments' => 'zahlungsausgaenge', @@ -3670,6 +3697,7 @@ $self->{texts} = { 'list_of_transactions' => 'buchungsliste', 'male' => 'männlich', 'mark as paid' => 'als bezahlt markieren', + 'max filesize' => 'maximale Dateigröße', 'missing' => 'Fehlbestand', 'missing_br' => 'Fehl.', 'month' => 'Monatliche Abgabe', diff --git a/sql/Pg-upgrade2/filemanagement_feature.sql b/sql/Pg-upgrade2/filemanagement_feature.sql new file mode 100644 index 000000000..4c8f8ca20 --- /dev/null +++ b/sql/Pg-upgrade2/filemanagement_feature.sql @@ -0,0 +1,13 @@ +-- @tag: filemanagement_feature +-- @description: "Zusätzliche Config flags für Filemanagement" +-- @depends: release_3_4_1 +ALTER TABLE defaults ADD COLUMN doc_delete_printfiles boolean DEFAULT false; +ALTER TABLE defaults ADD COLUMN doc_max_filesize integer DEFAULT 1000000; +ALTER TABLE defaults ADD COLUMN doc_storage boolean DEFAULT false; +ALTER TABLE defaults ADD COLUMN doc_storage_for_documents text default 'Filesystem'; +ALTER TABLE defaults ADD COLUMN doc_storage_for_attachments text default 'Filesystem'; +ALTER TABLE defaults ADD COLUMN doc_storage_for_images text default 'Filesystem'; +ALTER TABLE defaults ADD COLUMN doc_files boolean DEFAULT false; +ALTER TABLE defaults ADD COLUMN doc_files_rootpath text default ''; +ALTER TABLE defaults ADD COLUMN doc_webdav boolean DEFAULT false; +ALTER TABLE defaults ADD COLUMN doc_database boolean DEFAULT false; diff --git a/sql/Pg-upgrade2/files.sql b/sql/Pg-upgrade2/files.sql new file mode 100644 index 000000000..abbb39bf5 --- /dev/null +++ b/sql/Pg-upgrade2/files.sql @@ -0,0 +1,27 @@ +-- @tag: files +-- @description: Tabelle für Files +-- @charset: UTF-8 +-- @depends: release_3_4_1 +CREATE TABLE files( + id SERIAL PRIMARY KEY, + object_type TEXT NOT NULL, -- Tabellenname des Moduls z.B. customer, parts ... Fremdschlüssel Zusammen mit object_id + object_id INTEGER NOT NULL, -- Fremdschlüssel auf die id der Tabelle aus Spalte object_type + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, + mime_type TEXT NOT NULL, + source TEXT NOT NULL, + backend TEXT, + backend_data TEXT, + title varchar(45), + description TEXT, + itime TIMESTAMP DEFAULT now(), + mtime TIMESTAMP, + CONSTRAINT valid_type CHECK ( + (object_type = 'credit_note') OR (object_type = 'invoice') OR (object_type = 'sales_order') OR (object_type = 'sales_quotation') + OR (object_type = 'sales_delivery_order') OR (object_type = 'request_quotation') OR (object_type = 'purchase_order') + OR (object_type = 'purchase_delivery_order') OR (object_type = 'purchase_invoice') + OR (object_type = 'vendor') OR (object_type = 'customer') OR (object_type = 'part') OR (object_type = 'gl_transaction') + OR (object_type = 'dunning') OR (object_type = 'dunning1') OR (object_type = 'dunning2') OR (object_type = 'dunning3') + OR (object_type = 'draft') OR (object_type = 'statement')) +); + diff --git a/templates/webpages/client_config/_attachments.html b/templates/webpages/client_config/_attachments.html new file mode 100644 index 000000000..7d74bb21a --- /dev/null +++ b/templates/webpages/client_config/_attachments.html @@ -0,0 +1,30 @@ +[%- USE LxERP -%][%- USE L -%] +<div id="attachments"> +[% INCLUDE 'file/rename_dialog.html' %] + <table> + <tr><td class="listheading" colspan="4">[% LxERP.t8("Global Attachments") %] + [% LxERP.t8("for Document types") %] +</td></tr> + <tr><td> <div class="tabwidget"> + <ul> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=sales_quotation&object_id=0&is_global=1">[% + LxERP.t8("Sales Quotations") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=sales_order&object_id=0&is_global=1">[% + LxERP.t8("Sales Orders") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=sales_delivery_order&object_id=0&is_global=1">[% + LxERP.t8("Sales Delivery Orders") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=invoice&object_id=0&is_global=1">[% + LxERP.t8("Invoices") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=request_quotation&object_id=0&is_global=1">[% + LxERP.t8("Request Quotations") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=purchase_order&object_id=0&is_global=1">[% + LxERP.t8("Purchase Orders") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=purchase_delivery_order&object_id=0&is_global=1">[% + LxERP.t8("Purchase Delivery Orders") %]</a></li> + <li><a href="controller.pl?action=File/list&file_type=attachment&object_type=purchase_invoice&object_id=0&is_global=1">[% + LxERP.t8("Purchase Invoices") %]</a></li> + </ul> + </div> + </td></tr> + </table> +</div> diff --git a/templates/webpages/client_config/_features.html b/templates/webpages/client_config/_features.html index a06f1e090..8e128f34e 100644 --- a/templates/webpages/client_config/_features.html +++ b/templates/webpages/client_config/_features.html @@ -1,4 +1,5 @@ [%- USE LxERP -%][%- USE L -%][%- USE P -%][%- USE T8 %] +[% SET style="width: 250px" %] <div id="features"> <table> <tr><td class="listheading" colspan="4">[% LxERP.t8("DATEV") %]</td></tr> @@ -17,8 +18,7 @@ <td>[% LxERP.t8('Use UStVA') %]</td> </tr> - <tr><td class="listheading" colspan="4">[% LxERP.t8("WebDAV") %]</td></tr> - + <tr><td class="listheading" colspan="4">[% LxERP.t8("File Management") %]</td></tr> <tr> <td align="right">[% LxERP.t8('WebDAV') %]</td> <td>[% L.yes_no_tag('defaults.webdav', SELF.defaults.webdav) %]</td> @@ -29,6 +29,63 @@ <td>[% L.yes_no_tag('defaults.webdav_documents', SELF.defaults.webdav_documents) %]</td> <td>[% LxERP.t8('Save document in WebDAV repository') %]</td> </tr> + <tr> + <td align="right">[% LxERP.t8('Filemanagement') %]</td> + <td>[% L.yes_no_tag('defaults.doc_storage', SELF.defaults.doc_storage) %]</td> + <td>[% LxERP.t8('Use Filemanagement') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Storage Type for generated/imported PDF Documents') %]</td> + <td>[% L.select_tag('defaults.doc_storage_for_documents', + [ [ 'Filesystem', LxERP.t8('Files') ],[ 'Webdav', LxERP.t8('WebDAV') ],[ 'ExtDMS', LxERP.t8('ext.DMS') ],[ 'DB', LxERP.t8('Database') ] ], + default = SELF.defaults.doc_storage_for_documents) %]</td> + <td>[% LxERP.t8('Use this storage backend for all generated PDF-Files') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Storage Type for Attachments') %]</td> + <td>[% L.select_tag('defaults.doc_storage_for_attachments', + [ [ 'Filesystem', LxERP.t8('Files') ],[ 'Webdav', LxERP.t8('WebDAV') ],[ 'ExtDMS', LxERP.t8('ext.DMS') ],[ 'DB', LxERP.t8('Database') ] ], + default = SELF.defaults.doc_storage_for_attachments) %]</td> + <td>[% LxERP.t8('Use this storage backend for all uploaded attachments') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Storage Type for images') %]</td> + <td>[% L.select_tag('defaults.doc_storage_for_images', + [ [ 'Filesystem', LxERP.t8('Files') ],[ 'Webdav', LxERP.t8('WebDAV') ],[ 'ExtDMS', LxERP.t8('ext.DMS') ],[ 'DB', LxERP.t8('Database') ] ], + default = SELF.defaults.doc_storage_for_images) %]</td> + <td>[% LxERP.t8('Use this storage backend for uploaded images') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Delete printfiles') %]</td> + <td>[% L.yes_no_tag('defaults.doc_delete_printfiles', SELF.defaults.doc_delete_printfiles) %]</td> + <td>[% LxERP.t8('Allow to delete generated printfiles') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('max filesize') %]</td> + <td>[% L.input_tag('defaults.doc_max_filesize',SELF.defaults.doc_max_filesize, size=>10) %]</td> + <td>[% LxERP.t8('The maximum of uploadable filesize') %]</td> + </tr> + <tr><td class="listheading" colspan="4">[% LxERP.t8("Storage Backends") %]</td></tr> + <tr> + <td align="right">[% LxERP.t8('Files') %]</td> + <td>[% L.yes_no_tag('defaults.doc_files', SELF.defaults.doc_files) %]</td> + <td>[% LxERP.t8('Use File Storage backend') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Root path for file storage') %]</td> + <td>[% L.input_tag('defaults.doc_files_rootpath',SELF.defaults.doc_files_rootpath, style=style) %]</td> + <td>[% LxERP.t8('This is the root directory for the File storage backend, must be writable for webserver') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('WebDAV') %]</td> + <td>[% L.yes_no_tag('defaults.doc_webdav', SELF.defaults.doc_webdav) %]</td> + <td>[% LxERP.t8('Use WebDAV Storage backend') %]</td> + </tr> + <tr> + <td align="right">[% LxERP.t8('Database') %]</td> + <td>[% L.yes_no_tag('defaults.doc_database', SELF.defaults.doc_database) %]</td> + <td>[% LxERP.t8('Use Database Storage backend (not implemented yet!)') %]</td> + </tr> <tr><td class="listheading" colspan="4">[% LxERP.t8("Reports") %]</td></tr> @@ -81,7 +138,7 @@ </tr> <tr> <td align="right">[% LxERP.t8('CSS style for pictures') %]</td> - <td> [% L.input_tag('defaults.parts_image_css', SELF.defaults.parts_image_css, style=style) %]</td> + <td> [% L.input_tag('defaults.parts_image_css',SELF.defaults.parts_image_css, style=style) %]</td> <td>[% LxERP.t8('Style the picture with the following CSS code') %]</td> </tr> <tr> diff --git a/templates/webpages/client_config/form.html b/templates/webpages/client_config/form.html index 56403108a..20935eeb4 100644 --- a/templates/webpages/client_config/form.html +++ b/templates/webpages/client_config/form.html @@ -62,6 +62,9 @@ $(function() { <li><a href="#datev_check_configuration">[% LxERP.t8('DATEV check configuration') %]</a></li> [% END %] <li><a href="#orders_deleteable">[% LxERP.t8('Orders / Delivery Orders deleteable') %]</a></li> +[%- IF INSTANCE_CONF.get_doc_storage %] + <li><a href="#attachments">[% LxERP.t8('Global Attachments') %]</a></li> +[%- END %] <li><a href="#warehouse">[% LxERP.t8('Warehouse') %]</a></li> <li><a href="#features">[% LxERP.t8('Features') %]</a></li> </ul> @@ -71,6 +74,9 @@ $(function() { [% PROCESS 'client_config/_posting_configuration.html' %] [% PROCESS 'client_config/_datev_check_configuration.html' %] [% PROCESS 'client_config/_orders_deleteable.html' %] +[%- IF INSTANCE_CONF.get_doc_storage %] +[% PROCESS 'client_config/_attachments.html' %] +[%- END %] [% PROCESS 'client_config/_warehouse.html' %] [% PROCESS 'client_config/_features.html' %] [% PROCESS 'client_config/_miscellaneous.html' %] diff --git a/templates/webpages/common/search_history.html b/templates/webpages/common/search_history.html index 53b92e876..eb8074f95 100644 --- a/templates/webpages/common/search_history.html +++ b/templates/webpages/common/search_history.html @@ -76,11 +76,14 @@ <script type="text/javascript"> <!-- - var defaults = ['SAVED', 'DELETED', 'ADDED', 'PAYMENT POSTED', 'POSTED', 'POSTED AS NEW', 'SAVED FOR DUNNING', 'DUNNING STARTED', 'PRINTED']; + var defaults = ['SAVED', 'DELETED', 'ADDED', 'PAYMENT POSTED', 'POSTED', + 'POSTED AS NEW', 'SAVED FOR DUNNING', 'DUNNING STARTED', 'PRINTED', + 'QUEUED', 'CANCELED' ,'IMPORT', 'UNIMPORT' ]; var available; var selected; var translated = { 'SAVED' : '[% 'SAVED' | $T8 %]', + 'SCREENED' : '[% 'SCREENED' | $T8 %]', 'DELETED' : '[% 'DELETED' | $T8 %]', 'ADDED' : '[% 'ADDED' | $T8 %]', 'PAYMENT POSTED' : '[% 'PAYMENT POSTED' | $T8 %]', @@ -89,11 +92,15 @@ 'SAVED FOR DUNNING' : '[% 'SAVED FOR DUNNING' | $T8 %]', 'DUNNING STARTED' : '[% 'DUNNING STARTED' | $T8 %]', 'PRINTED' : '[% 'PRINTED' | $T8 %]', + 'QUEUED' : '[% 'QUEUED' | $T8 %]', + 'CANCELED' : '[% 'CANCELED' | $T8 %]', + 'IMPORT' : '[% 'IMPORT' | $T8 %]', + 'UNIMPORT' : '[% 'UNIMPORT' | $T8 %]', }; function addForm(index) { $('#inputHead').show(); - selected.push(available.splice(index, 1)); + selected.push(available.splice(index.index-1, 1)); $('#inputText').html($(selected).map(function(){ return translated[this]; }).get().join('<br>')); $('#einschraenkungen').val(selected.join(',')); diff --git a/templates/webpages/file/rename_dialog.html b/templates/webpages/file/rename_dialog.html new file mode 100644 index 000000000..c98521f8d --- /dev/null +++ b/templates/webpages/file/rename_dialog.html @@ -0,0 +1,18 @@ +[%- USE LxERP -%][%- USE L -%] +<div class="loading" id="rename_dialog" style="display:none" > + <div style="padding-bottom: 15px"> + <div> + <table> + <tr><td colspan="2" id="rename_extra_text"></td></tr> + <tr><td colspan="2">[%- LxERP.t8("Please modify filename") %]:</td></tr> + <tr><td colspan="2"><input size='40' name="newfilename" id="newfilename_id" value=""></td></tr> + <tr><td><input type="hidden" name="next_ids" id="next_ids_id" value=""> + <input type="hidden" name="sessionfile" id="sessionfile_id" value=""> + <input type="hidden" name="rename_id" id="rename_id_id" value=""> + <input type="hidden" name="is_global" id="is_global_id" value=""> + [% L.button_tag("return kivi.File.renameaction();", LxERP.t8('Continue'), id => "rename_cont_btn") %]</td> + <td>[% L.button_tag("return kivi.File.renameclose();" , LxERP.t8('Cancel') , class => "submit") %]</td></tr> + </table> + </div> + </div> +</div> -- 2.20.1