Merge branch '2020-leistungsdatum'
authorMoritz Bunkus <m.bunkus@linet.de>
Thu, 3 Dec 2020 13:13:09 +0000 (14:13 +0100)
committerMoritz Bunkus <m.bunkus@linet.de>
Thu, 3 Dec 2020 13:13:09 +0000 (14:13 +0100)
52 files changed:
SL/AR.pm
SL/ClientJS.pm
SL/Controller/CsvImport/Base.pm
SL/Controller/CsvImport/Part.pm
SL/Controller/MassInvoiceCreatePrint.pm
SL/Controller/Order.pm
SL/DB/CsvImportProfile.pm
SL/DB/Helper/AttrDuration.pm
SL/DB/Object.pm
SL/Form.pm
SL/Helper/Inventory.pm [new file with mode: 0644]
SL/Helper/Inventory/Allocation.pm [new file with mode: 0644]
SL/LXDebug.pm
SL/Layout/Base.pm
SL/Presenter/Tag.pm
SL/X.pm
bin/mozilla/ar.pl
bin/mozilla/gl.pl
bin/mozilla/io.pl
bin/mozilla/oe.pl
bin/mozilla/sepa.pl
css/kivitendo/main.css
css/lx-office-erp/main.css
doc/changelog
doc/dokumentation.xml
doc/html/ch02s02.html
doc/html/ch02s06.html
doc/html/ch02s07.html
doc/html/ch02s13.html
doc/html/ch03.html
doc/html/ch03s03.html
doc/html/ch03s07.html
doc/html/ch03s08.html
doc/html/ch03s09.html
doc/html/ch03s10.html
doc/html/ch04.html
doc/html/index.html
doc/kivitendo-Dokumentation.pdf
image/glass14x14.png [new file with mode: 0644]
js/client_js.js
js/kivi.MassInvoiceCreatePrint.js
js/kivi.js
locale/de/all
locale/en/all
t/db_helper/attr_duration.t
t/wh/inventory.t [new file with mode: 0644]
templates/print/marei/kiviletter.sty
templates/webpages/ar/search.html
templates/webpages/csv_import/form.html
templates/webpages/gl/form_header.html
templates/webpages/ir/form_header.html
templates/webpages/project/_basic_data.html

index 30fb5e8..b856f44 100644 (file)
--- a/SL/AR.pm
+++ b/SL/AR.pm
@@ -481,6 +481,7 @@ sub ar_transactions {
 
   my $query =
     qq|SELECT DISTINCT a.id, a.invnumber, a.ordnumber, a.cusordnumber, a.transdate, | .
+    qq|  a.donumber, a.deliverydate, | .
     qq|  a.duedate, a.netamount, a.amount, a.paid, | .
     qq|  a.invoice, a.datepaid, a.notes, a.shipvia, | .
     qq|  a.shippingpoint, a.storno, a.storno_id, a.globalproject_id, | .
@@ -682,7 +683,7 @@ SQL
   my $sortdir   = !defined $form->{sortdir} ? 'ASC' : $form->{sortdir} ? 'ASC' : 'DESC';
   my $sortorder = join(', ', map { "$_ $sortdir" } @a);
 
-  if (grep({ $_ eq $form->{sort} } qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description department))) {
+  if (grep({ $_ eq $form->{sort} } qw(id transdate duedate invnumber ordnumber cusordnumber donumber deliverydate name datepaid employee shippingpoint shipvia transaction_description department))) {
     $sortorder = $form->{sort} . " $sortdir";
   }
 
index 336f971..7d8c362 100644 (file)
@@ -123,6 +123,8 @@ my %supported_methods = (
   run_once_for           => 3,  # kivi.run_once_for(<TARGET>, <ARGS>)
 
   scroll_into_view       => 1,  # $(<TARGET>)[0].scrollIntoView()
+
+  set_cursor_position    => 2,  # kivi.set_cursor_position(<TARGET>, <ARGS>)
 );
 
 my %trim_target_for = map { ($_ => 1) } qw(insertAfter insertBefore appendTo prependTo);
index e3c119d..663be5e 100644 (file)
@@ -45,8 +45,8 @@ sub run {
 
   $self->controller->track_progress(progress => 10);
 
-  my $old_numberformat      = $::myconfig{numberformat};
-  $::myconfig{numberformat} = $self->controller->profile->get('numberformat');
+  local $::myconfig{numberformat} = $self->controller->profile->get('numberformat');
+  local $::myconfig{dateformat}   = $self->controller->profile->get('dateformat');
 
   $self->csv->parse;
 
@@ -83,8 +83,6 @@ sub run {
   $self->fix_field_lengths;
 
   $self->controller->track_progress(progress => 100);
-
-  $::myconfig{numberformat} = $old_numberformat;
 }
 
 sub add_columns {
index 83e09d6..ecd212c 100644 (file)
@@ -680,7 +680,7 @@ sub handle_makemodel {
     }
   }
 
-  $entry->{part}->makemodels([ $entry->{part}->makemodels_sorted, @new_makemodels ]) if @new_makemodels;
+  $entry->{part}->makemodels([ @{$entry->{part}->makemodels_sorted}, @new_makemodels ]) if @new_makemodels;
 
   # reindex makemodels
   my $i = 0;
index aab6373..acc300e 100644 (file)
@@ -58,13 +58,23 @@ sub action_create_invoices {
 
   my $db = SL::DB::Invoice->new->db;
   my @invoices;
+  my @already_closed_delivery_orders;
 
   if (!$db->with_transaction(sub {
     foreach my $id (@sales_delivery_order_ids) {
       my $delivery_order    = SL::DB::DeliveryOrder->new(id => $id)->load;
 
-      my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
-      push @invoices, $invoice;
+      # Only process open delivery orders. In this list should only be open
+      # delivery orders, but if the user clicked browser back, a new creation
+      # of invoices for delivery orders which are closed now can be triggered.
+      # Prevent this.
+      if ($delivery_order->closed) {
+        push @already_closed_delivery_orders, $delivery_order;
+
+      } else {
+        my $invoice = $delivery_order->convert_to_invoice() || die $db->error;
+        push @invoices, $invoice;
+      }
     }
 
     1;
@@ -76,7 +86,13 @@ sub action_create_invoices {
   my $key = sprintf('%d-%d', Time::HiRes::gettimeofday());
   $::auth->set_session_value("MassInvoiceCreatePrint::ids-${key}" => [ map { $_->id } @invoices ]);
 
-  flash_later('info', t8('The invoices have been created. They\'re pre-selected below.'));
+  if (@already_closed_delivery_orders) {
+    my $dos_list = join ' ', map { $_->donumber } @already_closed_delivery_orders;
+    flash_later('error', t8('The following delivery orders could not be processed because they are already closed: #1', $dos_list));
+  }
+
+  flash_later('info', t8('The invoices have been created. They\'re pre-selected below.')) if @invoices;
+
   $self->redirect_to(action => 'list_invoices', ids => $key);
 }
 
@@ -89,6 +105,11 @@ sub action_list_invoices {
   if ($::form->{ids}) {
     my $key = 'MassInvoiceCreatePrint::ids-' . $::form->{ids};
     $self->invoice_ids($::auth->get_session_value($key) || []);
+
+    # Prevent models->get to retrieve any invoices if session key is there
+    # but no ids are given.
+    $self->invoice_ids([0]) if !@{$self->invoice_ids};
+
     $self->invoice_models->add_additional_url_params(ids => $::form->{ids});
   }
 
@@ -377,9 +398,11 @@ sub setup_list_sales_delivery_orders_action_bar {
         ],
         action => [
           t8("Create and print invoices for all selected delivery orders"),
-          call     => [ 'kivi.MassInvoiceCreatePrint.submitMassCreationForm' ],
-          disabled => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
-          only_if  => $params{show_creation_buttons},
+          submit    => [ 'form', { action => 'MassInvoiceCreatePrint/create_invoices' } ],
+          disabled  => !$params{num_rows} ? $::locale->text('The report doesn\'t contain entries.') : undef,
+          only_if   => $params{show_creation_buttons},
+          checks    => [ 'kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection' ],
+          only_once => 1,
         ],
 
         action => [
index 606f985..b51802f 100644 (file)
@@ -262,6 +262,7 @@ sub action_print {
   my $formname    = $::form->{print_options}->{formname};
   my $copies      = $::form->{print_options}->{copies};
   my $groupitems  = $::form->{print_options}->{groupitems};
+  my $printer_id  = $::form->{print_options}->{printer_id};
 
   # only pdf and opendocument by now
   if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf)) {
@@ -286,6 +287,7 @@ sub action_print {
   my @errors = generate_pdf($self->order, \$pdf, { format     => $format,
                                                    formname   => $formname,
                                                    language   => $self->order->language,
+                                                   printer_id => $printer_id,
                                                    groupitems => $groupitems });
   if (scalar @errors) {
     return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render;
@@ -433,6 +435,7 @@ sub action_send_email {
                                                     format     => $::form->{print_options}->{format},
                                                     formname   => $::form->{print_options}->{formname},
                                                     language   => $self->order->language,
+                                                    printer_id => $::form->{print_options}->{printer_id},
                                                     groupitems => $::form->{print_options}->{groupitems}});
     if (scalar @errors) {
       return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self);
@@ -578,27 +581,10 @@ sub action_get_has_active_periodic_invoices {
 sub action_save_and_delivery_order {
   my ($self) = @_;
 
-  my $errors = $self->save();
-
-  if (scalar @{ $errors }) {
-    $self->js->flash('error', $_) foreach @{ $errors };
-    return $self->js->render();
-  }
-
-  my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been saved')
-           : $self->type eq purchase_order_type()    ? $::locale->text('The order has been saved')
-           : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been saved')
-           : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
-           : '';
-  flash_later('info', $text);
-
-  my @redirect_params = (
+  $self->save_and_redirect_to(
     controller => 'oe.pl',
     action     => 'oe_delivery_order_from_order',
-    id         => $self->order->id,
   );
-
-  $self->redirect_to(@redirect_params);
 }
 
 # save the order and redirect to the frontend subroutine for a new
@@ -606,27 +592,10 @@ sub action_save_and_delivery_order {
 sub action_save_and_invoice {
   my ($self) = @_;
 
-  my $errors = $self->save();
-
-  if (scalar @{ $errors }) {
-    $self->js->flash('error', $_) foreach @{ $errors };
-    return $self->js->render();
-  }
-
-  my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been saved')
-           : $self->type eq purchase_order_type()    ? $::locale->text('The order has been saved')
-           : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been saved')
-           : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
-           : '';
-  flash_later('info', $text);
-
-  my @redirect_params = (
+  $self->save_and_redirect_to(
     controller => 'oe.pl',
     action     => 'oe_invoice_from_order',
-    id         => $self->order->id,
   );
-
-  $self->redirect_to(@redirect_params);
 }
 
 # workflow from sales quotation to sales order
@@ -643,27 +612,10 @@ sub action_purchase_order {
 sub action_save_and_ap_transaction {
   my ($self) = @_;
 
-  my $errors = $self->save();
-
-  if (scalar @{ $errors }) {
-    $self->js->flash('error', $_) foreach @{ $errors };
-    return $self->js->render();
-  }
-
-  my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been saved')
-           : $self->type eq purchase_order_type()    ? $::locale->text('The order has been saved')
-           : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been saved')
-           : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
-           : '';
-  flash_later('info', $text);
-
-  my @redirect_params = (
+  $self->save_and_redirect_to(
     controller => 'ap.pl',
     action     => 'add_from_purchase_order',
-    id         => $self->order->id,
   );
-
-  $self->redirect_to(@redirect_params);
 }
 
 # set form elements in respect to a changed customer or vendor
@@ -1917,6 +1869,7 @@ sub generate_pdf {
   $print_form->{format}      = $params->{format}   || 'pdf';
   $print_form->{media}       = $params->{media}    || 'file';
   $print_form->{groupitems}  = $params->{groupitems};
+  $print_form->{printer_id}  = $params->{printer_id};
   $print_form->{media}       = 'file'                             if $print_form->{media} eq 'screen';
 
   $order->language($params->{language});
@@ -1935,7 +1888,7 @@ sub generate_pdf {
     extension   => $template_ext,
     email       => $print_form->{media} eq 'email',
     language    => $params->{language},
-    printer_id  => $print_form->{printer_id},  # todo
+    printer_id  => $print_form->{printer_id},
   );
 
   if (!defined $template_file) {
@@ -2107,6 +2060,26 @@ sub nr_key {
        : '';
 }
 
+sub save_and_redirect_to {
+  my ($self, %params) = @_;
+
+  my $errors = $self->save();
+
+  if (scalar @{ $errors }) {
+    $self->js->flash('error', $_) foreach @{ $errors };
+    return $self->js->render();
+  }
+
+  my $text = $self->type eq sales_order_type()       ? $::locale->text('The order has been saved')
+           : $self->type eq purchase_order_type()    ? $::locale->text('The order has been saved')
+           : $self->type eq sales_quotation_type()   ? $::locale->text('The quotation has been saved')
+           : $self->type eq request_quotation_type() ? $::locale->text('The rfq has been saved')
+           : '';
+  flash_later('info', $text);
+
+  $self->redirect_to(%params, id => $self->order->id);
+}
+
 1;
 
 __END__
@@ -2238,8 +2211,6 @@ java script functions
 
 =item * check for direct delivery (workflow sales order -> purchase order)
 
-=item * language / part translations
-
 =item * access rights
 
 =item * display weights
index bf11279..b816bed 100644 (file)
@@ -38,6 +38,7 @@ sub set_defaults {
                        escape_char  => '"',
                        charset      => 'CP850',
                        numberformat => $::myconfig{numberformat},
+                       dateformat   => $::myconfig{dateformat},
                        duplicates   => 'no_check',
                       );
 
index 5bdd4bf..c627f19 100644 (file)
@@ -116,6 +116,22 @@ sub _make_minutes {
     my $as_minutes = "${attribute}_as_minutes";
     return defined($self->$attribute) ? sprintf('%d:%02d', $self->$as_hours, $self->$as_minutes) : undef;
   };
+
+  *{ $package . '::' . $attribute . '_in_hours' } = sub {
+    my ($self, $value) = @_;
+
+    $self->$attribute(int($value * 60 + 0.5)) if @_ > 1;
+    return $self->$attribute / 60.0;
+  };
+
+  *{ $package . '::' . $attribute . '_in_hours_as_number' } = sub {
+    my ($self, $value) = @_;
+
+    my $sub = "${attribute}_in_hours";
+
+    $self->$sub($::form->parse_amount(\%::myconfig, $value)) if @_ > 1;
+    return $::form->format_amount(\%::myconfig, $self->$sub, 2);
+  };
 }
 
 1;
@@ -234,6 +250,17 @@ Access the full value as a formatted string in the form C<h:mm>,
 e.g. C<1:30> for the value 90 minutes. Parsing such a string is
 supported, too.
 
+=item C<attribute_in_hours [$new_value]>
+
+Access the full value but convert to and from hours when
+reading/writing the value.
+
+=item C<attribute_in_hours_as_number [$new_value]>
+
+Access the full value but convert to and from hours when
+reading/writing the value. The value is formatted to/parsed from the
+user's number format.
+
 =back
 
 =head1 FUNCTIONS
index 3e0fca6..89d15c6 100755 (executable)
@@ -7,6 +7,7 @@ use English qw(-no_match_vars);
 use Rose::DB::Object;
 use Rose::DB::Object::Constants qw();
 use List::MoreUtils qw(any pairwise);
+use List::Util qw(first);
 
 use SL::DB;
 use SL::DB::Helper::Attr;
@@ -105,6 +106,50 @@ sub update_attributes {
   return $self;
 }
 
+sub update_collection {
+  my ($self, $attribute, $entries) = @_;
+
+  my $self_primary_key = "" . ($self->meta->primary_key_columns)[0];
+
+  croak "\$self hasn't been saved yet" if !$self->$self_primary_key;
+
+  my $relationship = first { $_->name eq $attribute } @{ $self->meta->relationships };
+
+  croak "No relationship found for attribute '$attribute'" if !$relationship;
+
+  my @primary_key_columns = $relationship->class->meta->primary_key_columns;
+
+  croak "Classes with multiple primary key columns are not supported" if scalar(@primary_key_columns) > 1;
+
+  my $class             = $relationship->class;
+  my $manager_class     = "SL::DB::Manager::" . substr($class, 8);
+  my $other_primary_key = "" . $primary_key_columns[0];
+  my $column_map        = $relationship->column_map;
+  my @new_entries       = @{ $entries          // [] };
+  my @existing_entries  = @{ $self->$attribute // [] };
+  my @to_delete         = grep { my $value = $_->$other_primary_key; !any { $_->{$other_primary_key} == $value } @new_entries } @existing_entries;
+
+  $_->delete for @to_delete;
+
+  foreach my $entry (@new_entries) {
+    if (!$entry->{$other_primary_key}) {
+      my $new_instance = $class->new(%{ $entry });
+
+      foreach my $self_attribute (keys %{ $column_map }) {
+        my $other_attribute = $column_map->{$self_attribute};
+        $new_instance->$other_attribute($self->$self_attribute);
+      }
+
+      $new_instance->save;
+
+      next;
+    }
+
+    my $existing = first { $_->$other_primary_key == $entry->{$other_primary_key} } @existing_entries;
+    $existing->update_attributes(%{ $entry }) if $existing;
+  }
+}
+
 sub call_sub {
   my $self = shift;
   my $sub  = shift;
@@ -318,6 +363,28 @@ Assigns the attributes from C<%attributes> by calling the
 C<assign_attributes> function and saves the object afterwards. Returns
 the object itself.
 
+=item C<update_collection $attribute, $entries, %params>
+
+Updates a one-to-many relationship named C<$attribute> to match the
+entries in C<$entries>. C<$entries> is supposed to be an array ref of
+hash refs.
+
+For each hash ref in C<$entries> that does not contain a field for the
+relationship's primary key column, this function creates a new entry
+in the database with its attributes set to the data in the entry.
+
+For each hash ref in C<$entries> that contains a field for the
+relationship's primary key column, this function looks up the
+corresponding entry in C<$self-&gt;$attribute> & updates its
+attributes with the data in the entry.
+
+All objects in C<$self-&gt;$attribute> for which no corresponding
+entry exists in C<$entries> are deleted by calling the object's
+C<delete> method.
+
+In all cases the relationship itself C<$self-&gt;$attribute> is not
+changed.
+
 =item _get_manager_class
 
 Returns the manager package for the object or class that it is called
index fa6e70a..c92d267 100644 (file)
@@ -382,10 +382,11 @@ sub create_http_response {
     my $session_cookie_value = $main::auth->get_session_id();
 
     if ($session_cookie_value) {
-      $session_cookie = $cgi->cookie('-name'   => $main::auth->get_session_cookie_name(),
-                                     '-value'  => $session_cookie_value,
-                                     '-path'   => $uri->path,
-                                     '-secure' => $::request->is_https);
+      $session_cookie = $cgi->cookie('-name'    => $main::auth->get_session_cookie_name(),
+                                     '-value'   => $session_cookie_value,
+                                     '-path'    => $uri->path,
+                                     '-expires' => '+' . $::auth->{session_timeout} . 'm',
+                                     '-secure'  => $::request->is_https);
     }
   }
 
diff --git a/SL/Helper/Inventory.pm b/SL/Helper/Inventory.pm
new file mode 100644 (file)
index 0000000..da3e5b1
--- /dev/null
@@ -0,0 +1,779 @@
+package SL::Helper::Inventory;
+
+use strict;
+use Carp;
+use DateTime;
+use Exporter qw(import);
+use List::Util qw(min sum);
+use List::UtilsBy qw(sort_by);
+use List::MoreUtils qw(any);
+use POSIX qw(ceil);
+
+use SL::Locale::String qw(t8);
+use SL::MoreCommon qw(listify);
+use SL::DBUtils qw(selectall_hashref_query selectrow_query);
+use SL::DB::TransferType;
+use SL::Helper::Number qw(_format_number _round_number);
+use SL::Helper::Inventory::Allocation;
+use SL::X;
+
+our @EXPORT_OK = qw(get_stock get_onhand allocate allocate_for_assembly produce_assembly check_constraints);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
+
+sub _get_stock_onhand {
+  my (%params) = @_;
+
+  my $onhand_mode = !!$params{onhand};
+
+  my @selects = (
+    'SUM(qty) AS qty',
+    'MIN(EXTRACT(epoch FROM inventory.itime)) AS itime',
+  );
+  my @values;
+  my @where;
+  my @groups;
+
+  if ($params{part}) {
+    my @ids = map { ref $_ ? $_->id : $_ } listify($params{part});
+    push @where, sprintf "parts_id IN (%s)", join ', ', ("?") x @ids;
+    push @values, @ids;
+  }
+
+  if ($params{bin}) {
+    my @ids = map { ref $_ ? $_->id : $_ } listify($params{bin});
+    push @where, sprintf "bin_id IN (%s)", join ', ', ("?") x @ids;
+    push @values, @ids;
+  }
+
+  if ($params{warehouse}) {
+    my @ids = map { ref $_ ? $_->id : $_ } listify($params{warehouse});
+    push @where, sprintf "warehouse.id IN (%s)", join ', ', ("?") x @ids;
+    push @values, @ids;
+  }
+
+  if ($params{chargenumber}) {
+    my @ids = listify($params{chargenumber});
+    push @where, sprintf "chargenumber IN (%s)", join ', ', ("?") x @ids;
+    push @values, @ids;
+  }
+
+  if ($params{date}) {
+    Carp::croak("not DateTime ".$params{date}) unless ref($params{date}) eq 'DateTime';
+    push @where, sprintf "shippingdate <= ?";
+    push @values, $params{date};
+  }
+
+  if (!$params{bestbefore} && $onhand_mode && default_show_bestbefore()) {
+    $params{bestbefore} = DateTime->now_local;
+  }
+
+  if ($params{bestbefore}) {
+    Carp::croak("not DateTime ".$params{date}) unless ref($params{bestbefore}) eq 'DateTime';
+    push @where, sprintf "(bestbefore IS NULL OR bestbefore >= ?)";
+    push @values, $params{bestbefore};
+  }
+
+  # by
+  my %allowed_by = (
+    part          => [ qw(parts_id) ],
+    bin           => [ qw(bin_id inventory.warehouse_id)],
+    warehouse     => [ qw(inventory.warehouse_id) ],
+    chargenumber  => [ qw(chargenumber) ],
+    bestbefore    => [ qw(bestbefore) ],
+    for_allocate  => [ qw(parts_id bin_id inventory.warehouse_id chargenumber bestbefore) ],
+  );
+
+  if ($params{by}) {
+    for (listify($params{by})) {
+      my $selects = $allowed_by{$_} or Carp::croak("unknown option for by: $_");
+      push @selects, @$selects;
+      push @groups,  @$selects;
+    }
+  }
+
+  my $select   = join ',', @selects;
+  my $where    = @where  ? 'WHERE ' . join ' AND ', @where : '';
+  my $group_by = @groups ? 'GROUP BY ' . join ', ', @groups : '';
+
+  my $query = <<"";
+    SELECT $select FROM inventory
+    LEFT JOIN bin ON bin_id = bin.id
+    LEFT JOIN warehouse ON bin.warehouse_id = warehouse.id
+    $where
+    $group_by
+
+  if ($onhand_mode) {
+    $query .= ' HAVING SUM(qty) > 0';
+  }
+
+  my $results = selectall_hashref_query($::form, SL::DB->client->dbh, $query, @values);
+
+  my %with_objects = (
+    part         => 'SL::DB::Manager::Part',
+    bin          => 'SL::DB::Manager::Bin',
+    warehouse    => 'SL::DB::Manager::Warehouse',
+  );
+
+  my %slots = (
+    part      =>  'parts_id',
+    bin       =>  'bin_id',
+    warehouse =>  'warehouse_id',
+  );
+
+  if ($params{by} && $params{with_objects}) {
+    for my $with_object (listify($params{with_objects})) {
+      Carp::croak("unknown with_object $with_object") if !exists $with_objects{$with_object};
+
+      my $manager = $with_objects{$with_object};
+      my $slot = $slots{$with_object};
+      next if !(my @ids = map { $_->{$slot} } @$results);
+      my $objects = $manager->get_all(query => [ id => \@ids ]);
+      my %objects_by_id = map { $_->id => $_ } @$objects;
+
+      $_->{$with_object} = $objects_by_id{$_->{$slot}} for @$results;
+    }
+  }
+
+  if ($params{by}) {
+    return $results;
+  } else {
+    return $results->[0]{qty};
+  }
+}
+
+sub get_stock {
+  _get_stock_onhand(@_, onhand => 0);
+}
+
+sub get_onhand {
+  _get_stock_onhand(@_, onhand => 1);
+}
+
+sub allocate {
+  my (%params) = @_;
+
+  croak('allocate needs a part') unless $params{part};
+  croak('allocate needs a qty')  unless $params{qty};
+
+  my $part = $params{part};
+  my $qty  = $params{qty};
+
+  return () if $qty <= 0;
+
+  my $results = get_stock(part => $part, by => 'for_allocate');
+  my %bin_whitelist = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{bin});
+  my %wh_whitelist  = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{warehouse});
+  my %chargenumbers = map { (ref $_ ? $_->id : $_) => 1 } grep defined, listify($params{chargenumber});
+
+  # sort results so that chargenumbers are matched first, then wanted bins, then wanted warehouses
+  my @sorted_results = sort {
+       exists $chargenumbers{$b->{chargenumber}}  <=> exists $chargenumbers{$a->{chargenumber}} # then prefer wanted chargenumbers
+    || exists $bin_whitelist{$b->{bin_id}}        <=> exists $bin_whitelist{$a->{bin_id}}       # then prefer wanted bins
+    || exists $wh_whitelist{$b->{warehouse_id}}   <=> exists $wh_whitelist{$a->{warehouse_id}}  # then prefer wanted bins
+    || $a->{itime}                                <=> $b->{itime}                               # and finally prefer earlier charges
+  } @$results;
+  my @allocations;
+  my $rest_qty = $qty;
+
+  for my $chunk (@sorted_results) {
+    my $qty = min($chunk->{qty}, $rest_qty);
+
+    # since allocate operates on stock, this also ensures that no negative stock results are used
+    if ($qty > 0) {
+      push @allocations, SL::Helper::Inventory::Allocation->new(
+        parts_id          => $chunk->{parts_id},
+        qty               => $qty,
+        comment           => $params{comment},
+        bin_id            => $chunk->{bin_id},
+        warehouse_id      => $chunk->{warehouse_id},
+        chargenumber      => $chunk->{chargenumber},
+        bestbefore        => $chunk->{bestbefore},
+        for_object_id     => undef,
+      );
+      $rest_qty -=  _round_number($qty, 5);
+    }
+    $rest_qty = _round_number($rest_qty, 5);
+    last if $rest_qty == 0;
+  }
+  if ($rest_qty > 0) {
+    die SL::X::Inventory::Allocation->new(
+      error => 'not enough to allocate',
+      msg => t8("can not allocate #1 units of #2, missing #3 units", _format_number($qty), $part->displayable_name, _format_number($rest_qty)),
+    );
+  } else {
+    if ($params{constraints}) {
+      check_constraints($params{constraints},\@allocations);
+    }
+    return @allocations;
+  }
+}
+
+sub allocate_for_assembly {
+  my (%params) = @_;
+
+  my $part = $params{part} or Carp::croak('allocate needs a part');
+  my $qty  = $params{qty}  or Carp::croak('allocate needs a qty');
+
+  Carp::croak('not an assembly') unless $part->is_assembly;
+
+  my %parts_to_allocate;
+
+  for my $assembly ($part->assemblies) {
+    $parts_to_allocate{ $assembly->part->id } //= 0;
+    $parts_to_allocate{ $assembly->part->id } += $assembly->qty * $qty;
+  }
+
+  my @allocations;
+
+  for my $part_id (keys %parts_to_allocate) {
+    my $part = SL::DB::Part->load_cached($part_id);
+    push @allocations, allocate(%params, part => $part, qty => $parts_to_allocate{$part_id});
+  }
+
+  @allocations;
+}
+
+sub check_constraints {
+  my ($constraints, $allocations) = @_;
+  if ('CODE' eq ref $constraints) {
+    if (!$constraints->(@$allocations)) {
+      die SL::X::Inventory::Allocation->new(
+        error => 'allocation constraints failure',
+        msg => t8("Allocations didn't pass constraints"),
+      );
+    }
+  } else {
+    croak 'constraints needs to be a hashref' unless 'HASH' eq ref $constraints;
+
+    my %supported_constraints = (
+      bin_id       => 'bin_id',
+      warehouse_id => 'warehouse_id',
+      chargenumber => 'chargenumber',
+    );
+
+    for (keys %$constraints ) {
+      croak "unsupported constraint '$_'" unless $supported_constraints{$_};
+      next unless defined $constraints->{$_};
+
+      my %whitelist = map { (ref $_ ? $_->id : $_) => 1 } listify($constraints->{$_});
+      my $accessor = $supported_constraints{$_};
+
+      if (any { !$whitelist{$_->$accessor} } @$allocations) {
+        my %error_constraints = (
+          bin_id         => t8('Bins'),
+          warehouse_id   => t8('Warehouses'),
+          chargenumber   => t8('Chargenumbers'),
+        );
+        my @allocs = grep { $whitelist{$_->$accessor} } @$allocations;
+        my $needed = sum map { $_->qty } grep { !$whitelist{$_->$accessor} } @$allocations;
+        my $err    = t8("Cannot allocate parts.");
+        $err      .= ' '.t8('part \'#\'1 in bin \'#2\' only with qty #3 (need additional #4) and chargenumber \'#5\'.',
+              SL::DB::Part->load_cached($_->parts_id)->description,
+              SL::DB::Bin->load_cached($_->bin_id)->full_description,
+              _format_number($_->qty), _format_number($needed), $_->chargenumber ? $_->chargenumber : '--') for @allocs;
+        die SL::X::Inventory::Allocation->new(
+          error => 'allocation constraints failure',
+          msg   => $err,
+        );
+      }
+    }
+  }
+}
+
+sub produce_assembly {
+  my (%params) = @_;
+
+  my $part = $params{part} or Carp::croak('produce_assembly needs a part');
+  my $qty  = $params{qty}  or Carp::croak('produce_assembly needs a qty');
+
+  my $allocations = $params{allocations};
+  if ($params{auto_allocate}) {
+    Carp::croak("produce_assembly: can't have both allocations and auto_allocate") if $params{allocations};
+    $allocations = [ allocate_for_assembly(part => $part, qty => $qty) ];
+  } else {
+    Carp::croak("produce_assembly: need allocations or auto_allocate to produce something") if !$params{allocations};
+    $allocations = $params{allocations};
+  }
+
+  my $bin          = $params{bin} or Carp::croak("need target bin");
+  my $chargenumber = $params{chargenumber};
+  my $bestbefore   = $params{bestbefore};
+  my $for_object_id = $params{for_object_id};
+  my $comment      = $params{comment} // '';
+
+  my $invoice               = $params{invoice};
+  my $project               = $params{project};
+
+  my $shippingdate = $params{shippingsdate} // DateTime->now_local;
+
+  my $trans_id              = $params{trans_id};
+  ($trans_id) = selectrow_query($::form, SL::DB->client->dbh, qq|SELECT nextval('id')| ) unless $trans_id;
+
+  my $trans_type_out = SL::DB::Manager::TransferType->find_by(direction => 'out', description => 'used');
+  my $trans_type_in  = SL::DB::Manager::TransferType->find_by(direction => 'in', description => 'assembled');
+
+  # check whether allocations are sane
+  if (!$params{no_check_allocations} && !$params{auto_allocate}) {
+    my %allocations_by_part = map { $_->parts_id  => $_->qty } @$allocations;
+    for my $assembly ($part->assemblies) {
+      $allocations_by_part{ $assembly->parts_id } -= $assembly->qty * $qty;
+    }
+
+    die "allocations are insufficient for production" if any { $_ < 0 } values %allocations_by_part;
+  }
+
+  my @transfers;
+  for my $allocation (@$allocations) {
+    my $oe_id = delete $allocation->{for_object_id};
+    push @transfers, $allocation->transfer_object(
+      trans_id     => $trans_id,
+      qty          => -$allocation->qty,
+      trans_type   => $trans_type_out,
+      shippingdate => $shippingdate,
+      employee     => SL::DB::Manager::Employee->current,
+    );
+  }
+
+  push @transfers, SL::DB::Inventory->new(
+    trans_id          => $trans_id,
+    trans_type        => $trans_type_in,
+    part              => $part,
+    qty               => $qty,
+    bin               => $bin,
+    warehouse         => $bin->warehouse_id,
+    chargenumber      => $chargenumber,
+    bestbefore        => $bestbefore,
+    shippingdate      => $shippingdate,
+    project           => $project,
+    invoice           => $invoice,
+    comment           => $comment,
+    employee          => SL::DB::Manager::Employee->current,
+    oe_id             => $for_object_id,
+  );
+
+  SL::DB->client->with_transaction(sub {
+    $_->save for @transfers;
+    1;
+  }) or do {
+    die SL::DB->client->error;
+  };
+
+  @transfers;
+}
+
+sub default_show_bestbefore {
+  $::instance_conf->get_show_bestbefore
+}
+
+1;
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::WH - Warehouse and Inventory API
+
+=head1 SYNOPSIS
+
+  # See description for an intro to the concepts used here.
+
+  use SL::Helper::Inventory qw(:ALL);
+
+  # stock, get "what's there" for a part with various conditions:
+  my $qty = get_stock(part => $part);                              # how much is on stock?
+  my $qty = get_stock(part => $part, date => $date);               # how much was on stock at a specific time?
+  my $qty = get_stock(part => $part, bin => $bin);                 # how much is on stock in a specific bin?
+  my $qty = get_stock(part => $part, warehouse => $warehouse);     # how much is on stock in a specific warehouse?
+  my $qty = get_stock(part => $part, chargenumber => $chargenumber); # how much is on stock of a specific chargenumber?
+
+  # onhand, get "what's available" for a part with various conditions:
+  my $qty = get_onhand(part => $part);                              # how much is available?
+  my $qty = get_onhand(part => $part, date => $date);               # how much was available at a specific time?
+  my $qty = get_onhand(part => $part, bin => $bin);                 # how much is available in a specific bin?
+  my $qty = get_onhand(part => $part, warehouse => $warehouse);     # how much is available in a specific warehouse?
+  my $qty = get_onhand(part => $part, chargenumber => $chargenumber); # how much is availbale of a specific chargenumber?
+
+  # onhand batch mode:
+  my $data = get_onhand(
+    warehouse    => $warehouse,
+    by           => [ qw(bin part chargenumber) ],
+    with_objects => [ qw(bin part) ],
+  );
+
+  # allocate:
+  my @allocations = allocate(
+    part         => $part,          # part_id works too
+    qty          => $qty,           # must be positive
+    chargenumber => $chargenumber,  # optional, may be arrayref. if provided these charges will be used first
+    bestbefore   => $datetime,      # optional, defaults to today. items with bestbefore prior to that date wont be used
+    bin          => $bin,           # optional, may be arrayref. if provided
+  );
+
+  # shortcut to allocate all that is needed for producing an assembly, will use chargenumbers as appropriate
+  my @allocations = allocate_for_assembly(
+    part         => $assembly,      # part_id works too
+    qty          => $qty,           # must be positive
+  );
+
+  # create allocation manually, bypassing checks. all of these need to be passed, even undefs
+  my $allocation = SL::Helper::Inventory::Allocation->new(
+    part_id           => $part->id,
+    qty               => 15,
+    bin_id            => $bin_obj->id,
+    warehouse_id      => $bin_obj->warehouse_id,
+    chargenumber      => '1823772365',
+    bestbefore        => undef,
+    for_object_id     => $order->id,
+  );
+
+  # produce_assembly:
+  produce_assembly(
+    part         => $part,           # target assembly
+    qty          => $qty,            # qty
+    allocations  => \@allocations,   # allocations to use. alternatively use "auto_allocate => 1,"
+
+    # where to put it
+    bin          => $bin,           # needed unless a global standard target is configured
+    chargenumber => $chargenumber,  # optional
+    bestbefore   => $datetime,      # optional
+    comment      => $comment,       # optional
+  );
+
+=head1 DESCRIPTION
+
+New functions for the warehouse and inventory api.
+
+The WH api currently has three large shortcomings: It is very hard to just get
+the current stock for an item, it's extremely complicated to use it to produce
+assemblies while ensuring that no stock ends up negative, and it's very hard to
+use it to get an overview over the actual contents of the inventory.
+
+The first problem has spawned several dozen small functions in the program that
+try to implement that, and those usually miss some details. They may ignore
+bestbefore times, comments, ignore negative quantities etc.
+
+To get this cleaned up a bit this code introduces two concepts: stock and onhand.
+
+=over 4
+
+=item * Stock is defined as the actual contents of the inventory, everything that is
+there.
+
+=item * Onhand is what is available, which means things that are stocked,
+not expired and not in any other way reserved for other uses.
+
+=back
+
+The two new functions C<get_stock> and C<get_onhand> encapsulate these principles and
+allow simple access with some optional filters for chargenumbers or warehouses.
+Both of them have a batch mode that can be used to get these information to
+supplement simple reports.
+
+To address the safe assembly creation a new function has been added.
+C<allocate> will try to find the requested quantity of a part in the inventory
+and will return allocations of it which can then be used to create the
+assembly. Allocation will happen with the C<onhand> semantics defined above,
+meaning that by default no expired goods will be used. The caller can supply
+hints of what shold be used and in those cases chargenumbers will be used up as
+much as possible first. C<allocate> will always try to fulfil the request even
+beyond those. Should the required amount not be stocked, allocate will throw an
+exception.
+
+C<produce_assembly> has been rewritten to only accept parameters about the
+target of the production, and requires allocations to complete the request. The
+allocations can be supplied manually, or can be generated automatically.
+C<produce_assembly> will check whether enough allocations are given to create
+the assembly, but will not check whether the allocations are backed. If the
+allocations are not sufficient or if the auto-allocation fails an exception
+is returned. If you need to produce something that is not in the inventory, you
+can bypass those checks by creating the allocations yourself (see
+L</"ALLOCATION DATA STRUCTURE">).
+
+Note: this is only intended to cover the scenarios described above. For other cases:
+
+=over 4
+
+=item *
+
+If you need actual inventory objects because of record links or something like
+that load them directly. And strongly consider redesigning that, because it's
+really fragile.
+
+=item *
+
+You need weight or accounting information you're on your own. The inventory api
+only concerns itself with the raw quantities.
+
+=item *
+
+If you need the first stock date of parts, or anything related to a specific
+transfer type or direction, this is not covered yet.
+
+=back
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item * get_stock PARAMS
+
+Returns for single parts how much actually exists in the inventory.
+
+Options:
+
+=over 4
+
+=item * part
+
+The part. Must be present without C<by>. May be arrayref with C<by>. Can be object or id.
+
+=item * bin
+
+If given, will only return stock on these bins. Optional. May be array, May be object or id.
+
+=item * warehouse
+
+If given, will only return stock on these warehouses. Optional. May be array, May be object or id.
+
+=item * date
+
+If given, will return stock as it were on this timestamp. Optional. Must be L<DateTime> object.
+
+=item * chargenumber
+
+If given, will only show stock with this chargenumber. Optional. May be array.
+
+=item * by
+
+See L</"STOCK/ONHAND REPORT MODE">
+
+=item * with_objects
+
+See L</"STOCK/ONHAND REPORT MODE">
+
+=back
+
+Will return a single qty normally, see L</"STOCK/ONHAND REPORT MODE"> for batch
+mode when C<by> is given.
+
+=item * get_onhand PARAMS
+
+Returns for single parts how much is available in the inventory. That excludes
+stock with expired bestbefore.
+
+It takes the same options as L</get_stock>.
+
+=over 4
+
+=item * bestbefore
+
+If given, will only return stock with a bestbefore at or after the given date.
+Optional. Must be L<DateTime> object.
+
+=back
+
+=item * allocate PARAMS
+
+Accepted parameters:
+
+=over 4
+
+=item * part
+
+=item * qty
+
+=item * bin
+
+Bin object. Optional.
+
+=item * warehouse
+
+Warehouse object. Optional.
+
+=item * chargenumber
+
+Optional.
+
+=item * bestbefore
+
+Datetime. Optional.
+
+=back
+
+Tries to allocate the required quantity using what is currently onhand. If
+given any of C<bin>, C<warehouse>, C<chargenumber>
+
+=item * allocate_for_assembly PARAMS
+
+Shortcut to allocate everything for an assembly. Takes the same arguments. Will
+compute the required amount for each assembly part and allocate all of them.
+
+=item * produce_assembly
+
+
+=back
+
+=head1 STOCK/ONHAND REPORT MODE
+
+If the special option C<by> is given with an arrayref, the result will instead
+be an arrayref of partitioned stocks by those fields. Valid partitions are:
+
+=over 4
+
+=item * part
+
+If this is given, part is optional in the parameters
+
+=item * bin
+
+=item * warehouse
+
+=item * chargenumber
+
+=item * bestbefore
+
+=back
+
+Note: If you want to use the returned data to create allocations you I<need> to
+enable all of these. To make this easier a special shortcut exists
+
+In this mode, C<with_objects> can be used to load C<warehouse>, C<bin>,
+C<parts>  objects in one go, just like with Rose. They
+need to be present in C<by> before that though.
+
+=head1 ALLOCATION ALGORITHM
+
+When calling allocate, the current onhand (== available stock) of the item will
+be used to decide which bins/chargenumbers/bestbefore can be used.
+
+In general allocate will try to make the request happen, and will use the
+provided charges up first, and then tap everything else. If you need to only
+I<exactly> use the provided charges, you'll need to craft the allocations
+yourself. See L</"ALLOCATION DATA STRUCTURE"> for that.
+
+If C<chargenumber> is given, those will be used up next.
+
+After that normal quantities will be used.
+
+These are tiebreakers and expected to rarely matter in reality. If you need
+finegrained control over which allocation is used, you may want to get the
+onhands yourself and select the appropriate ones.
+
+Only quantities with C<bestbefore> unset or after the given date will be
+considered. If more than one charge is eligible, the earlier C<bestbefore>
+will be used.
+
+Allocations do NOT have an internal memory and can't react to other allocations
+of the same part earlier. Never double allocate the same part within a
+transaction.
+
+=head1 ALLOCATION DATA STRUCTURE
+
+Allocations are instances of the helper class C<SL::Helper::Inventory::Allocation>. They require
+each of the following attributes to be set at creation time:
+
+=over 4
+
+=item * parts_id
+
+=item * qty
+
+=item * bin_id
+
+=item * warehouse_id
+
+=item * chargenumber
+
+=item * bestbefore
+
+=item * for_object_id
+
+If set the allocations will be marked as allocated for the given object.
+If these allocations are later used to produce an assembly, the resulting
+consuming transactions will be marked as belonging to the given object.
+The object may be an order, productionorder or other objects
+
+=back
+
+C<chargenumber>, C<bestbefore> and C<for_object_id> and C<comment> may be
+C<undef> (but must still be present at creation time). Instances are considered
+immutable.
+
+Allocations also provide the method C<transfer_object> which will create a new
+C<SL::DB::Inventory> bject with all the playload.
+
+=head1 CONSTRAINTS
+
+  # whitelist constraints
+  ->allocate(
+    ...
+    constraints => {
+      bin_id       => \@allowed_bins,
+      chargenumber => \@allowed_chargenumbers,
+    }
+  );
+
+  # custom constraints
+  ->allocate(
+    constraints => sub {
+      # only allow chargenumbers with specific format
+      all { $_->chargenumber =~ /^ C \d{8} - \a{d2} $/x } @_
+
+      &&
+      # and must all have a bestbefore date
+      all { $_->bestbefore } @_;
+    }
+  )
+
+C<allocation> is "best effort" in nature. It will take the C<bin>,
+C<chargenumber> etc hints from the parameters, but will try it's bvest to
+fulfil the request anyway and only bail out if it is absolutely not possible.
+
+Sometimes you need to restrict allocations though. For this you can pass
+additional constraints to C<allocate>. A constraint serves as a whitelist.
+Every allocation must fulfil every constraint by having that attribute be one
+of the given values.
+
+In case even that is not enough, you may supply a custom check by passing a
+function that will be given the allocation objects.
+
+Note that both whitelists and constraints do not influence the order of
+allocations, which is done purely from the initial parameters. They only serve
+to reject allocations made in good faith which do fulfil required assertions.
+
+=head1 ERROR HANDLING
+
+C<allocate> and C<produce_assembly> will throw exceptions if the request can
+not be completed. The usual reason will be insufficient onhand to allocate, or
+insufficient allocations to process the request.
+
+=head1 KNOWN PROBLEMS
+
+  * It's not currently possible to identify allocations between requests, for
+    example for presenting the user possible allocations and then actually using
+    them on the next request.
+  * It's not currently possible to give C<allocate> prior constraints.
+    Currently all constraints are treated as hints (and will be preferred) but
+    the internal ordering of the hints is fixed and more complex preferentials
+    are not supported.
+  * bestbefore handling is untested
+  * interaction with config option "transfer_default_ignore_onhand" is
+    currently undefined (and implicitly ignores it)
+
+=head1 TODO
+
+  * define and describe error classes
+  * define wrapper classes for stock/onhand batch mode return values
+  * handle extra arguments in produce: shippingdate, project
+  * document no_ check
+  * tests
+
+=head1 BUGS
+
+None yet :)
+
+=head1 AUTHOR
+
+Sven Schöling E<lt>sven.schoeling@googlemail.comE<gt>
+
+=cut
diff --git a/SL/Helper/Inventory/Allocation.pm b/SL/Helper/Inventory/Allocation.pm
new file mode 100644 (file)
index 0000000..e406bf4
--- /dev/null
@@ -0,0 +1,65 @@
+package SL::Helper::Inventory::Allocation;
+
+use strict;
+
+my @attributes = qw(parts_id qty bin_id warehouse_id chargenumber bestbefore comment for_object_id);
+my %attributes = map { $_ => 1 } @attributes;
+my %mapped_attributes = (
+  for_object_id => 'oe_id',
+);
+
+for my $name (@attributes) {
+  no strict 'refs';
+  *{"SL::Helper::Inventory::Allocation::$name"} = sub { $_[0]{$name} };
+}
+
+sub new {
+  my ($class, %params) = @_;
+
+  Carp::croak("missing attribute $_") for grep { !exists $params{$_}     } @attributes;
+  Carp::croak("unknown attribute $_") for grep { !exists $attributes{$_} } keys %params;
+  Carp::croak("$_ must be set")       for grep { !$params{$_} } qw(parts_id qty bin_id);
+  Carp::croak("$_ must be positive")  for grep { !($params{$_} > 0) } qw(parts_id qty bin_id);
+
+  bless { %params }, $class;
+}
+
+sub transfer_object {
+  my ($self, %params) = @_;
+
+  SL::DB::Inventory->new(
+    (map {
+      my $attr = $mapped_attributes{$_} // $_;
+      $attr => $self->{$attr}
+    } @attributes),
+    %params,
+  );
+}
+
+1;
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::Helper::Inventory::Allocation - Inventory API allocation data structure
+
+=head1 SYNOPSIS
+
+  # all of these need to be present
+  my $allocation = SL::Helper::Inventory::Allocation->new(
+    part_id           => $part->id,
+    qty               => 15,
+    bin_id            => $bin_obj->id,
+    warehouse_id      => $bin_obj->warehouse_id,
+    chargenumber      => '1823772365',           # can be undef
+    bestbefore        => undef,                  # can be undef
+    for_object_id     => $order->id,             # can be undef
+  );
+
+
+=head1 SEE ALSO
+
+The full documentation can be found in L<SL::Helper::Inventory>
+
+=cut
index 0503dcb..a1e1976 100644 (file)
@@ -19,8 +19,9 @@ use constant FILE_TARGET   => 0;
 use constant STDERR_TARGET => 1;
 
 use Data::Dumper;
+use List::MoreUtils qw(all);
 use POSIX qw(strftime getpid);
-use Scalar::Util qw(blessed refaddr weaken);
+use Scalar::Util qw(blessed refaddr weaken looks_like_number);
 use Time::HiRes qw(gettimeofday tv_interval);
 use SL::Request ();
 use SL::YAML;
@@ -231,8 +232,14 @@ sub dump_sql_result {
     map { $column_lengths{$_} = length $row->{$_} if (length $row->{$_} > $column_lengths{$_}) } keys %{ $row };
   }
 
+  my %alignment;
+  foreach my $column (keys %column_lengths) {
+    my $all_look_like_number = all { (($_->{$column} // '') eq '') || looks_like_number($_->{$column}) } @{ $results };
+    $alignment{$column}      = $all_look_like_number ? '' : '-';
+  }
+
   my @sorted_names = sort keys %column_lengths;
-  my $format       = join '|', map { '%' . $column_lengths{$_} . 's' } @sorted_names;
+  my $format       = join '|', map { '%'  . $alignment{$_} . $column_lengths{$_} . 's' } @sorted_names;
 
   $prefix .= ' ' if $prefix;
 
index df9c008..8e6acfc 100644 (file)
@@ -3,6 +3,7 @@ package SL::Layout::Base;
 use strict;
 use parent qw(Rose::Object);
 
+use File::Slurp qw(read_file);
 use List::MoreUtils qw(uniq);
 use Time::HiRes qw();
 
@@ -19,6 +20,7 @@ use Rose::Object::MakeMethods::Generic (
 
 use SL::Menu;
 use SL::Presenter;
+use SL::System::Process;
 
 my %menu_cache;
 
@@ -42,8 +44,21 @@ sub get {
 }
 
 sub init_auto_reload_resources_param {
-  return '' unless $::lx_office_conf{debug}->{auto_reload_resources};
-  return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000));
+  return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000)) if $::lx_office_conf{debug}->{auto_reload_resources};
+
+  my $git_dir = SL::System::Process::exe_dir() . '/.git';
+
+  return '' unless -d $git_dir;
+
+  my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
+
+  return '' unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
+
+  $content = eval { scalar(read_file($git_dir . '/' . $1)) };
+
+  return '' unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
+
+  return '?rand=' . $1;
 }
 
 ##########################################
index 3129437..8929dc5 100644 (file)
@@ -111,6 +111,10 @@ sub select_tag {
 
   _set_id_attribute(\%attributes, $name);
 
+  $collection         = [] if defined($collection) && !ref($collection) && ($collection eq '');
+
+  my $with_filter     = delete($attributes{with_filter});
+  my $fil_placeholder = delete($attributes{filter_placeholder});
   my $value_key       = delete($attributes{value_key})   || 'id';
   my $title_key       = delete($attributes{title_key})   || $value_key;
   my $default_key     = delete($attributes{default_key}) || 'selected';
@@ -206,7 +210,28 @@ sub select_tag {
     } @{ $collection };
   }
 
-  html_tag('select', $code, %attributes, name => $name);
+  my $select_html = html_tag('select', $code, %attributes, name => $name);
+
+  if ($with_filter) {
+    my $input_style;
+
+    if (($attributes{style} // '') =~ m{width: *(\d+) *px}i) {
+      $input_style = "width: " . ($1 - 22) . "px";
+    }
+
+    my $input_html = html_tag(
+      'input', undef,
+      autocomplete     => 'off',
+      type             => 'text',
+      id               => $attributes{id} . '_filter',
+      'data-select-id' => $attributes{id},
+      (placeholder     => $fil_placeholder) x !!$fil_placeholder,
+      (style           => $input_style)     x !!$input_style,
+    );
+    $select_html = html_tag('div', $input_html . $select_html, class => "filtered_select");
+  }
+
+  return $select_html;
 }
 
 sub checkbox_tag {
@@ -345,10 +370,11 @@ sub date_tag {
   $::request->layout->add_javascripts('kivi.Validator.js');
   $::request->presenter->need_reinit_widgets($params{id});
 
+  $params{'data-validate'} = join(' ', "date", grep { $_ } (delete $params{'data-validate'}));
+
   input_tag(
     $name, blessed($value) ? $value->to_lxoffice : $value,
     size   => 11,
-    "data-validate" => "date",
     %params,
     %class, @onchange,
   );
diff --git a/SL/X.pm b/SL/X.pm
index 9343dea..c3533e2 100644 (file)
--- a/SL/X.pm
+++ b/SL/X.pm
@@ -30,6 +30,16 @@ use Exception::Class (
   'SL::X::ZUGFeRDValidation' => {
     isa                 => 'SL::X::Base',
   },
+  'SL::X::Inventory' => {
+    isa                 => 'SL::X::Base',
+    fields              => [ qw(msg error) ],
+    defaults            => { error_template => [ '%s: %s', qw(msg) ] },
+  },
+  'SL::X::Inventory::Allocation' => {
+    isa                 => 'SL::X::Base',
+    fields              => [ qw(msg error) ],
+    defaults            => { error_template => [ '%s: %s', qw(msg) ] },
+  },
 );
 
 1;
index 8d49a50..51fbb1a 100644 (file)
@@ -1010,7 +1010,7 @@ sub ar_transactions {
   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
   @columns =
-    qw(ids transdate id type invnumber ordnumber cusordnumber name netamount tax amount paid
+    qw(ids transdate id type invnumber ordnumber cusordnumber donumber deliverydate name netamount tax amount paid
        datepaid due duedate transaction_description notes salesman employee shippingpoint shipvia
        marge_total marge_percent globalprojectnumber customernumber country ustid taxzone
        payment_terms charts customertype direct_debit dunning_description department);
@@ -1037,6 +1037,8 @@ sub ar_transactions {
     'invnumber'               => { 'text' => $locale->text('Invoice'), },
     'ordnumber'               => { 'text' => $locale->text('Order'), },
     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
+    'donumber'                => { 'text' => $locale->text('Delivery Order'), },
+    'deliverydate'            => { 'text' => $locale->text('Delivery Date'), },
     'name'                    => { 'text' => $locale->text('Customer'), },
     'netamount'               => { 'text' => $locale->text('Amount'), },
     'tax'                     => { 'text' => $locale->text('Tax'), },
@@ -1067,7 +1069,7 @@ sub ar_transactions {
     %column_defs_cvars,
   );
 
-  foreach my $name (qw(id transdate duedate invnumber ordnumber cusordnumber name datepaid employee shippingpoint shipvia transaction_description direct_debit department)) {
+  foreach my $name (qw(id transdate duedate invnumber ordnumber cusordnumber donumber deliverydate name datepaid employee shippingpoint shipvia transaction_description direct_debit department)) {
     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
   }
index 6f49848..4b70aef 100644 (file)
@@ -792,13 +792,6 @@ sub display_rows {
   $form->{totaldebit}  = 0;
   $form->{totalcredit} = 0;
 
-  my %project_labels = ();
-  my @project_values = ("");
-  foreach my $item (@{ $form->{"ALL_PROJECTS"} }) {
-    push(@project_values, $item->{"id"});
-    $project_labels{$item->{"id"}} = $item->{"projectnumber"};
-  }
-
   my %charts_by_id  = map { ($_->{id} => $_) } @{ $::form->{ALL_CHARTS} };
   my $default_chart = $::form->{ALL_CHARTS}[0];
   my $transdate     = $::form->{transdate} ? DateTime->from_kivitendo($::form->{transdate}) : DateTime->today_local;
@@ -914,26 +907,23 @@ sub display_rows {
       }
     }
 
-    my $projectnumber =
-      NTI($cgi->popup_menu('-name' => "project_id_$i",
-                           '-values' => \@project_values,
-                           '-labels' => \%project_labels,
-                           '-default' => $form->{"project_id_$i"} ));
-    my $projectnumber_hidden = qq|
-    <input type="hidden" name="project_id_$i" value="$form->{"project_id_$i"}">|;
+    my $projectnumber = SL::Presenter::Project::picker("project_id_$i", $form->{"project_id_$i"});
+    my $projectnumber_hidden = SL::Presenter::Tag::hidden_tag("project_id_$i", $form->{"project_id_$i"});
 
-    my $balance = $form->format_amount(\%::myconfig, $balances{$accno_id} // 0, 2, 'DRCR');
+    my $copy2credit = $i == 1 ? 'onkeyup="copy_debit_to_credit()"' : '';
+    my $balance     = $form->format_amount(\%::myconfig, $balances{$accno_id} // 0, 2, 'DRCR');
 
     # if we have a bt_chart_id we disallow changing the amount of the bank account
     if ($form->{bt_chart_id}) {
       $debitreadonly = $creditreadonly = "readonly" if ($form->{"accno_id_$i"} eq $form->{bt_chart_id});
+      $copy2credit   = '' if $i == 1;   # and disallow copy2credit
     }
 
     print qq|<tr valign=top>
     $accno
     <td id="chart_balance_$i" align="right">${balance}</td>
     $fx_transaction
-    <td><input name="debit_$i" size="8" value="$form->{"debit_$i"}" accesskey=$i $debitreadonly></td>
+    <td><input name="debit_$i" size="8" value="$form->{"debit_$i"}" accesskey=$i $copy2credit $debitreadonly></td>
     <td><input name="credit_$i" size=8 value="$form->{"credit_$i"}" $creditreadonly></td>
     <td><input type="hidden" name="tax_$i" value="$form->{"tax_$i"}">$form->{"tax_$i"}</td>
     $tax_ddbox|;
@@ -1097,16 +1087,15 @@ sub form_header {
 
   my ($init) = @_;
 
-  $::request->layout->add_javascripts("autocomplete_chart.js", "kivi.File.js", "kivi.GL.js", "kivi.RecordTemplate.js");
-
-  my @old_project_ids = grep { $_ } map{ $::form->{"project_id_$_"} } 1..$::form->{rowcount};
+  $::request->layout->add_javascripts("autocomplete_chart.js", "autocomplete_project.js", "kivi.File.js", "kivi.GL.js", "kivi.RecordTemplate.js");
 
-  $::form->get_lists("projects"  => { "key"       => "ALL_PROJECTS",
-                                    "all"       => 0,
-                                    "old_id"    => \@old_project_ids },
+  my @old_project_ids     = grep { $_ } map{ $::form->{"project_id_$_"} } 1..$::form->{rowcount};
+  my @conditions          = @old_project_ids ? (id => \@old_project_ids) : ();
+  $::form->{ALL_PROJECTS} = SL::DB::Manager::Project->get_all_sorted(query => [ or => [ active => 1, @conditions ]]);
 
-                   "charts"    => { "key"       => "ALL_CHARTS",
-                                    "transdate" => $::form->{transdate} });
+  $::form->get_lists(
+    "charts"    => { "key" => "ALL_CHARTS", "transdate" => $::form->{transdate} },
+  );
 
   # we cannot book on charttype header
   @{ $::form->{ALL_CHARTS} } = grep { $_->{charttype} ne 'H' }  @{ $::form->{ALL_CHARTS} };
index 62a1d1d..9ca15b8 100644 (file)
@@ -328,7 +328,7 @@ sub display_row {
       $ship_qty          /= ( $all_units->{$form->{"unit_$i"}}->{factor} || 1 );
 
       $column_data{ship}  = $form->format_amount(\%myconfig, $form->round_amount($ship_qty, 2) * 1) . ' ' . $form->{"unit_$i"}
-      . $cgi->hidden(-name => "ship_$i", -value => $form->format_amount(\%myconfig, $form->{"ship_$i"}, $qty_dec));
+      . $cgi->hidden(-name => "ship_$i", -value => $form->{"ship_$i"}, $qty_dec);
 
       my $ship_missing_qty    = $form->{"qty_$i"} - $ship_qty;
       my $ship_missing_amount = $form->round_amount($ship_missing_qty * $form->{"sellprice_$i"} * (100 - $form->{"discount_$i"}) / 100 / $price_factor, 2);
index fa284f0..d2cceea 100644 (file)
@@ -2050,8 +2050,7 @@ sub delivery_order {
   $main::lxdebug->leave_sub();
 }
 
-sub oe_delivery_order_from_order {
-
+sub oe_prepare_xyz_from_order {
   return if !$::form->{id};
 
   my $order = SL::DB::Order->new(id => $::form->{id})->load;
@@ -2068,27 +2067,15 @@ sub oe_delivery_order_from_order {
   $::form->{rowcount}++;
 
   _update_ship();
+}
+
+sub oe_delivery_order_from_order {
+  oe_prepare_xyz_from_order();
   delivery_order();
 }
 
 sub oe_invoice_from_order {
-
-  return if !$::form->{id};
-
-  my $order = SL::DB::Order->new(id => $::form->{id})->load;
-  $order->flatten_to_form($::form, format_amounts => 1);
-
-  # hack: add partsgroup for first row if it does not exists,
-  # because _remove_billed_or_delivered_rows and _remove_full_delivered_rows
-  # determine fields to handled by existing fields for the first row. If partsgroup
-  # is missing there, for deleted rows the partsgroup_field is not emptied and in
-  # update_delivery_order it will not considered an empty row ...
-  $::form->{partsgroup_1} = '' if !exists $::form->{partsgroup_1};
-
-  # fake last empty row
-  $::form->{rowcount}++;
-
-  _update_ship();
+  oe_prepare_xyz_from_order();
   invoice();
 }
 
@@ -2180,10 +2167,12 @@ sub edit_periodic_invoices_config {
                };
   }
   # for older configs, replace email preset text if not yet set.
-  $config->{email_subject} ||= GenericTranslations->get(language_id => $::form->{lanuage_id},
-                                              translation_type =>"preset_text_periodic_invoices_email_subject");
-  $config->{email_body}    ||= GenericTranslations->get(language_id => $::form->{lanuage_id},
-                                              translation_type =>"preset_text_periodic_invoices_email_body");
+  $config->{email_subject} ||= GenericTranslations->get(language_id => $::form->{lanuage_id}, translation_type => "preset_text_periodic_invoices_email_subject");
+  $config->{email_body}    ||= GenericTranslations->get(language_id => $::form->{lanuage_id}, translation_type => "salutation_general")
+                             . GenericTranslations->get(language_id => $::form->{lanuage_id}, translation_type => "salutation_punctuation_mark")
+                             . "\n\n"
+                             . GenericTranslations->get(language_id => $::form->{lanuage_id}, translation_type => "preset_text_periodic_invoices_email_body");
+  $config->{email_body}      =~ s{\A[ \n\r]+|[ \n\r]+\Z}{}g;
 
   $config->{periodicity}             = 'm' if none { $_ eq $config->{periodicity}             }       @SL::DB::PeriodicInvoicesConfig::PERIODICITIES;
   $config->{order_value_periodicity} = 'p' if none { $_ eq $config->{order_value_periodicity} } ('p', @SL::DB::PeriodicInvoicesConfig::ORDER_VALUE_PERIODICITIES);
@@ -2257,7 +2246,7 @@ sub _remove_full_delivered_rows {
     next unless $::form->{"id_$row"};
     my $base_factor = SL::DB::Manager::Unit->find_by(name => $::form->{"unit_$row"})->base_factor;
     my $base_qty = $::form->parse_amount(\%::myconfig, $::form->{"qty_$row"}) *  $base_factor;
-    my $ship_qty = $::form->parse_amount(\%::myconfig, $::form->{"ship_$row"}) *  $base_factor;
+    my $ship_qty = $::form->{"ship_$row"} *  $base_factor;
     #$main::lxdebug->message(LXDebug->DEBUG2(),"shipto=".$ship_qty." qty=".$base_qty);
 
     if (!$ship_qty || ($ship_qty < $base_qty)) {
index b78083e..ca4ad0a 100755 (executable)
@@ -749,7 +749,7 @@ sub setup_sepa_edit_transfer_action_bar {
         accesskey => 'enter',
         tooltip   => t8('Post payments for selected invoices'),
         checks    => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
-        only_if   => $params{show_post_payments_button},
+        disabled  => $params{show_post_payments_button} ? undef : t8('All payments have already been posted.'),
       ],
       action => [
         t8('Payment list'),
@@ -757,7 +757,7 @@ sub setup_sepa_edit_transfer_action_bar {
         accesskey => 'enter',
         tooltip   => t8('Download list of payments as PDF'),
         checks    => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
-        not_if    => $params{show_post_payments_button},
+        disabled  => $params{show_post_payments_button} ? t8('All payments must be posted before the payment list can be downloaded.') : undef,
       ],
     );
   }
index 0c22493..563bda9 100644 (file)
@@ -614,3 +614,25 @@ body > div.admin {
 .cke_button {
   padding: 0px; 6px !important;
 }
+
+/* selects with text filters */
+div.filtered_select input, div.filtered_select select {
+  display: block;
+}
+
+div.filtered_select input {
+  background-image: url(../../image/glass14x14.png);
+  background-repeat: no-repeat;
+  background-position: 2px 2px;
+  border-radius: 0px;
+  border: solid #a0a0a0 1px;
+  border-bottom: none;
+  padding: 0px;
+  padding-left: 20px;
+  margin: 0;
+  width: 500px;
+}
+
+div.filtered_select select {
+  width: 522px;
+}
index a2a4ad0..36fe035 100644 (file)
@@ -611,3 +611,25 @@ div.layout-actionbar .layout-actionbar-default-action {
 .cke_button {
   padding: 0px; 6px !important;
 }
+
+/* selects with text filters */
+div.filtered_select input, div.filtered_select select {
+  display: block;
+}
+
+div.filtered_select > input {
+  background-image: url(../../image/glass14x14.png);
+  background-repeat: no-repeat;
+  background-position: 2px 2px;
+  border-radius: 0px;
+  border: solid #a0a0a0 1px;
+  border-bottom: none;
+  padding: 0px;
+  padding-left: 20px;
+  margin: 0;
+  width: 500px;
+}
+
+div.filtered_select select {
+  width: 522px;
+}
index ddd2b18..42bbf5d 100644 (file)
@@ -6,13 +6,13 @@
 
 Mittelgroße neue Features:
 
- - Der Import von Bankauszuügen im MT940-Format wurde komplett neu
+ - Der Import von Bankauszügen im MT940-Format wurde komplett neu
    geschrieben. Das externe Programm AQBanking wird nun nicht mehr
    benötigt.
 
 Kleinere neue Features und Detailverbesserungen:
 
- - Neuer Order-Controller: Unterstütztung für Ãœbersetzungen von
+ - Neuer Order-Controller: Unterstützung für Ãœbersetzungen von
    Artikeln wurde implementiert.
  - Einkaufs-/Verkaufsbelege: die Belegsprache ist nun als Auswahl
    direkt in der Hauptmaske vorhanden und nicht mehr in den
@@ -40,6 +40,7 @@ Kleinere neue Features und Detailverbesserungen:
 
 Bugfixes (Tracker: https://www.kivitendo.de/redmine):
 97 Benutzer löschen unter System->Benutzer funktioniert nicht
+408 Neuer Auftragskontroller: Drucken von odt-Vorlagen geht nur mit Standardvorlage
 
 
 2020-10-02 - Release 3.5.6.1
index c73c1de..90fc6e5 100644 (file)
@@ -2172,7 +2172,7 @@ digits_year: 4</programlisting>
       debianoiden Betriebssystemen installiert man die Pakete mit:</para>
 
       <para><programlisting>apt install texlive-base-bin texlive-latex-recommended texlive-fonts-recommended \
-  texlive-latex-extra texlive-lang-german texlive-generic-extra texlive-xetex ghostscript</programlisting></para>
+  texlive-latex-extra texlive-lang-german ghostscript</programlisting></para>
 
       <para>Für Fedora benötigen Sie die folgenden Pakete:</para>
 
index 998fd65..27e104a 100644 (file)
   libimage-info-perl libgd-gd2-perl libapache2-mod-fcgid \
   libfile-copy-recursive-perl postgresql libalgorithm-checkdigits-perl \
   libcrypt-pbkdf2-perl git libcgi-pm-perl libtext-unidecode-perl libwww-perl\
-  postgresql-contrib aqbanking-tools poppler-utils libhtml-restrict-perl\
+  postgresql-contrib poppler-utils libhtml-restrict-perl\
   libdatetime-set-perl libset-infinite-perl liblist-utilsby-perl\
   libdaemon-generic-perl libfile-flock-perl libfile-slurp-perl\
   libfile-mimeinfo-perl libpbkdf2-tiny-perl libregexp-ipv6-perl \
           </pre><p>Zusätzlich müssen einige Pakete aus dem CPAN installiert
           werden. Dazu können Sie die folgenden Befehle anwenden:</p><pre class="programlisting">cpan DateTime::event::Cron DateTime::Set FCGI \
   HTML::Restrict PBKDF2::Tiny Rose::Db::Object Set::Infinite</pre></div></div><div class="sect2" title="2.2.3. Andere Pakete installieren"><div class="titlepage"><div><div><h3 class="title"><a name="d0e649"></a>2.2.3. Andere Pakete installieren</h3></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
-                     <code class="literal">aqbanking-tools</code> Für das Parsen des MT940 Bankformats (Version 6 oder höher)</p></li><li class="listitem"><p>
                      <code class="literal">poppler-utils</code> 'pdfinfo' zum Erkennen der Seitenanzahl bei der PDF-Generierung</p></li><li class="listitem"><p>
-                     <code class="literal">Postgres Trigram-Index</code> Für datenbankoptimierte Suchanfragen. Bspw. im Paket <code class="literal">postgresql-contrib</code> enthalten</p></li></ul></div><p>Debian und Ubuntu: </p><pre class="programlisting">apt install aqbanking-tools postgresql-contrib poppler-utils</pre><p>
-            </p><p>Fedora: </p><pre class="programlisting">dnf install aqbanking poppler-utils postgresql-contrib</pre><p>
-            </p><p>openSUSE: </p><pre class="programlisting">zypper install aqbanking-tools poppler-tools</pre><p>
+                     <code class="literal">Postgres Trigram-Index</code> Für datenbankoptimierte Suchanfragen. Bspw. im Paket <code class="literal">postgresql-contrib</code> enthalten</p></li></ul></div><p>Debian und Ubuntu: </p><pre class="programlisting">apt install postgresql-contrib poppler-utils</pre><p>
+            </p><p>Fedora: </p><pre class="programlisting">dnf install poppler-utils postgresql-contrib</pre><p>
+            </p><p>openSUSE: </p><pre class="programlisting">zypper install poppler-tools</pre><p>
             </p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch02.html">Zurück</a>&nbsp;</td><td width="20%" align="center"><a accesskey="u" href="ch02.html">Nach oben</a></td><td width="40%" align="right">&nbsp;<a accesskey="n" href="ch02s03.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">Kapitel 2. Installation und Grundkonfiguration&nbsp;</td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top">&nbsp;2.3. Manuelle Installation des Programmpaketes</td></tr></table></div></body></html>
\ No newline at end of file
index ef46abc..a9cd196 100644 (file)
@@ -1,6 +1,6 @@
 <html><head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-   <title>2.6. Webserver-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"><link rel="next" href="ch02s07.html" title="2.7. Der Task-Server"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.6. Webserver-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch02s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.6. Webserver-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Apache-Konfiguration"></a>2.6. Webserver-Konfiguration</h2></div></div></div><div class="sect2" title="2.6.1. Grundkonfiguration mittels CGI"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1129"></a>2.6.1. Grundkonfiguration mittels CGI</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Für einen deutlichen Performanceschub sorgt die Ausführung
+   <title>2.6. Webserver-Konfiguration</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch02.html" title="Kapitel 2. Installation und Grundkonfiguration"><link rel="prev" href="ch02s05.html" title="2.5. Anpassung der PostgreSQL-Konfiguration"><link rel="next" href="ch02s07.html" title="2.7. Der Task-Server"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2.6. Webserver-Konfiguration</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch02s05.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 2. Installation und Grundkonfiguration</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch02s07.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="2.6. Webserver-Konfiguration"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="Apache-Konfiguration"></a>2.6. Webserver-Konfiguration</h2></div></div></div><div class="sect2" title="2.6.1. Grundkonfiguration mittels CGI"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1123"></a>2.6.1. Grundkonfiguration mittels CGI</h3></div></div></div><div class="note" title="Anmerkung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Anmerkung]" src="system/docbook-xsl/images/note.png"></td><th align="left">Anmerkung</th></tr><tr><td align="left" valign="top"><p>Für einen deutlichen Performanceschub sorgt die Ausführung
           mittels FastCGI/FCGI. Die Einrichtung wird ausführlich im Abschnitt
           <a class="xref" href="ch02s06.html#Apache-Konfiguration.FCGI" title="2.6.2. Konfiguration für FastCGI/FCGI">Konfiguration für FastCGI/FCGI</a> beschrieben.</p></td></tr></table></div><p>Der Zugriff auf das Programmverzeichnis muss in der Apache
         Webserverkonfigurationsdatei <code class="literal">httpd.conf</code> eingestellt
@@ -104,13 +104,13 @@ AliasMatch ^/url/for/kivitendo-erp-fcgid/[^/]+\.pl /path/to/kivitendo-erp/dispat
 Alias       /url/for/kivitendo-erp-fcgid/          /path/to/kivitendo-erp/</pre><p>Dann ist unter <code class="filename">/url/for/kivitendo-erp/</code>
           die normale Version erreichbar, und unter
           <code class="constant">/url/for/kivitendo-erp-fcgid/</code> die
-          FastCGI-Version.</p></div></div><div class="sect2" title="2.6.3. Authentifizierung mittels HTTP Basic Authentication"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1283"></a>2.6.3. Authentifizierung mittels HTTP Basic Authentication</h3></div></div></div><p>
+          FastCGI-Version.</p></div></div><div class="sect2" title="2.6.3. Authentifizierung mittels HTTP Basic Authentication"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1277"></a>2.6.3. Authentifizierung mittels HTTP Basic Authentication</h3></div></div></div><p>
         Kivitendo unterstützt, dass Benutzerauthentifizierung Ã¼ber den Webserver mittels des Â»Basic«-HTTP-Authentifizierungs-Schema erfolgt
         (siehe <a class="ulink" href="https://tools.ietf.org/html/rfc7617" target="_top">RFC 7617</a>). Dazu ist es aber nötig, dass der dabei vom Client
         mitgeschickte Header <code class="constant">Authorization</code> vom Webserver an Kivitendo Ã¼ber die Umgebungsvariable
         <code class="constant">HTTP_AUTHORIZATION</code> weitergegeben wird, was standardmäßig nicht der Fall ist. Für Apache kann dies Ã¼ber die
         folgende Konfigurationsoption aktiviert werden:
-       </p><pre class="programlisting">SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1</pre></div><div class="sect2" title="2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1299"></a>2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</h3></div></div></div><p>
+       </p><pre class="programlisting">SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1</pre></div><div class="sect2" title="2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1293"></a>2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</h3></div></div></div><p>
         Aufgrund von aktuellen (Mitte 2020) Sicherheitswarnungen für git basierte Webanwendungen ist die mitausgelieferte .htaccess
         restriktiver geworden und verhindert somit das Auslesen von git basierten Daten.
         Für debian/ubuntu muss das Modul mod_rewrite einmalig so aktiviert werden:
@@ -125,13 +125,13 @@ Alias       /url/for/kivitendo-erp-fcgid/          /path/to/kivitendo-erp/</pre>
           Require all denied
         &lt;/DirectoryMatch&gt;</pre><p>
        
-            </p></div><div class="sect2" title="2.6.5. Weitergehende Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1313"></a>2.6.5. Weitergehende Konfiguration</h3></div></div></div><p>Für einen deutlichen Sicherheitsmehrwert sorgt die Ausführung
+            </p></div><div class="sect2" title="2.6.5. Weitergehende Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e1307"></a>2.6.5. Weitergehende Konfiguration</h3></div></div></div><p>Für einen deutlichen Sicherheitsmehrwert sorgt die Ausführung
         von kivitendo nur Ã¼ber https-verschlüsselten Verbindungen, sowie
         weiteren Zusatzmassnahmen, wie beispielsweise Basic Authenticate. Die
         Konfigurationsmöglichkeiten sprengen allerdings den Rahmen dieser
         Anleitung, hier ein Hinweis auf einen entsprechenden <a class="ulink" href="http://redmine.kivitendo-premium.de/boards/1/topics/142" target="_top">Foreneintrag
         (Stand Sept. 2015)</a> und einen aktuellen (Stand Mai 2017) <a class="ulink" href="https://mozilla.github.io/server-side-tls/ssl-config-generator/" target="_top">
-        SSL-Konfigurations-Generator</a>.</p></div><div class="sect3" title="2.6.1. Aktivierung von Apache2 modsecurity"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1324"></a>2.6.1. Aktivierung von Apache2 modsecurity</h4></div></div></div><p>Aufgrund des OpenSource Charakters ist kivitendo nicht "out of the box" sicher.
+        SSL-Konfigurations-Generator</a>.</p></div><div class="sect3" title="2.6.1. Aktivierung von Apache2 modsecurity"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1318"></a>2.6.1. Aktivierung von Apache2 modsecurity</h4></div></div></div><p>Aufgrund des OpenSource Charakters ist kivitendo nicht "out of the box" sicher.
   Organisatorisch empfehlen wir hier die enge Zusammenarbeit mit einem kivitendo Partner der auch in der
 Lage ist weiterführende Fragen in Bezug auf Datenschutz und Datensicherheit zu beantworten.
 Unabhängig davon empfehlen wir im Webserver Bereich die Aktivierung und Konfiguration des Moduls modsecurity für den Apache2, damit
index 07d7e2a..d5c1aae 100644 (file)
@@ -44,7 +44,7 @@
         Links aus einem der Runlevel-Verzeichnisse heraus in den Boot-Prozess
         einzubinden. Da das bei neueren Linux-Distributionen aber nicht
         zwangsläufig funktioniert, werden auch Start-Scripte mitgeliefert, die
-        anstelle eines symbolischen Links verwendet werden können.</p><div class="sect3" title="2.7.3.1. SystemV-basierende Systeme (z.B. Ã¤ltere Debian, Ã¤ltere openSUSE, Ã¤ltere Fedora)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1397"></a>2.7.3.1. SystemV-basierende Systeme (z.B. Ã¤ltere Debian, Ã¤ltere
+        anstelle eines symbolischen Links verwendet werden können.</p><div class="sect3" title="2.7.3.1. SystemV-basierende Systeme (z.B. Ã¤ltere Debian, Ã¤ltere openSUSE, Ã¤ltere Fedora)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1391"></a>2.7.3.1. SystemV-basierende Systeme (z.B. Ã¤ltere Debian, Ã¤ltere
           openSUSE, Ã¤ltere Fedora)</h4></div></div></div><p>Kopieren Sie die Datei
           <code class="filename">scripts/boot/system-v/kivitendo-task-server</code>
           nach <code class="filename">/etc/init.d/kivitendo-task-server</code>. Passen
           <code class="literal">DAEMON=....</code>). Binden Sie das Script in den
           Boot-Prozess ein. Dies ist distributionsabhängig:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Debian-basierende Systeme:</p><pre class="programlisting">update-rc.d kivitendo-task-server defaults
 insserv kivitendo-task-server</pre></li><li class="listitem"><p>Ältere openSUSE und Ã¤ltere Fedora:</p><pre class="programlisting">chkconfig --add kivitendo-task-server</pre></li></ul></div><p>Danach kann der Task-Server mit dem folgenden Befehl gestartet
-          werden:</p><pre class="programlisting">/etc/init.d/kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1426"></a>2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)</h4></div></div></div><p>Kopieren Sie die Datei
+          werden:</p><pre class="programlisting">/etc/init.d/kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1420"></a>2.7.3.2. Upstart-basierende Systeme (z.B. Ubuntu bis 14.04)</h4></div></div></div><p>Kopieren Sie die Datei
           <code class="filename">scripts/boot/upstart/kivitendo-task-server.conf</code>
           nach <code class="filename">/etc/init/kivitendo-task-server.conf</code>.
           Passen Sie in der kopierten Datei den Pfad zum Task-Server an (Zeile
           <code class="literal">exec ....</code>).</p><p>Danach kann der Task-Server mit dem folgenden Befehl gestartet
-          werden:</p><pre class="programlisting">service kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere Fedora, neuere Ubuntu und neuere Debians)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1444"></a>2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere
+          werden:</p><pre class="programlisting">service kivitendo-task-server start</pre></div><div class="sect3" title="2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere Fedora, neuere Ubuntu und neuere Debians)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e1438"></a>2.7.3.3. systemd-basierende Systeme (z.B. neure openSUSE, neuere
           Fedora, neuere Ubuntu und neuere Debians)</h4></div></div></div><p>Kopieren Sie die Datei
           <code class="filename">scripts/boot/systemd/kivitendo-task-server.service</code>
           nach <code class="filename">/etc/systemd/system/</code>. Passen Sie in der
index 74498e0..c95b04d 100644 (file)
       Verzeichnis umbenannt werden.</p><p>Dieses Verzeichnis, wie auch das komplette
       <code class="literal">users</code>-Verzeichnis, muss vom Webserver beschreibbar
       sein. Dieses wurde bereits erledigt (siehe <a class="xref" href="ch02s03.html" title="2.3. Manuelle Installation des Programmpaketes">Manuelle Installation des Programmpaketes</a>), kann aber erneut
-      Ã¼berprüft werden, wenn die Konvertierung nach PDF fehlschlägt.</p><div class="sect2" title="2.13.1. OpenDocument (odt) Druckvorlagen mit Makros"><div class="titlepage"><div><div><h3 class="title"><a name="d0e2420"></a>2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</h3></div></div></div><p>OpenDocument Vorlagen können Makros enthalten, welche komplexere
+      Ã¼berprüft werden, wenn die Konvertierung nach PDF fehlschlägt.</p><div class="sect2" title="2.13.1. OpenDocument (odt) Druckvorlagen mit Makros"><div class="titlepage"><div><div><h3 class="title"><a name="d0e2414"></a>2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</h3></div></div></div><p>OpenDocument Vorlagen können Makros enthalten, welche komplexere
         Aufgaben erfüllen.</p><p>Der Vorlagensatz "rev-odt" enthält solche Vorlagen mit <span class="bold"><strong>Schweizer Bank-Einzahlungsscheinen (BESR)</strong></span>.
         Diese Makros haben die Aufgabe, die in den Einzahlungsscheinen
         benötigte Referenznummer und Kodierzeile zu erzeugen. Hier eine kurze
         Beschreibung, wie die Makros aufgebaut sind, und was bei ihrer Nutzung
         zu beachten ist (<span class="bold"><strong>in fett sind nötige einmalige
-        Anpassungen aufgeführt</strong></span>):</p><div class="sect3" title="2.13.1.1. Bezeichnung der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2433"></a>2.13.1.1. Bezeichnung der Vorlagen</h4></div></div></div><p>Rechnung: invoice_besr.odt, Auftrag:
-          sales_order_besr.odt</p></div><div class="sect3" title="2.13.1.2. Vorbereitungen im Adminbereich"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2438"></a>2.13.1.2. Vorbereitungen im Adminbereich</h4></div></div></div><p>Damit beim Erstellen von Rechnungen und Aufträgen neben der
+        Anpassungen aufgeführt</strong></span>):</p><div class="sect3" title="2.13.1.1. Bezeichnung der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2427"></a>2.13.1.1. Bezeichnung der Vorlagen</h4></div></div></div><p>Rechnung: invoice_besr.odt, Auftrag:
+          sales_order_besr.odt</p></div><div class="sect3" title="2.13.1.2. Vorbereitungen im Adminbereich"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2432"></a>2.13.1.2. Vorbereitungen im Adminbereich</h4></div></div></div><p>Damit beim Erstellen von Rechnungen und Aufträgen neben der
           Standardvorlage ohne Einzahlungsschein weitere Vorlagen (z.B. mit
           Einzahlungsschein) auswählbar sind, muss für jedes Vorlagen-Suffix
           ein Drucker eingerichtet werden:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Druckeradministration â†’ Drucker hinzufügen</p></li><li class="listitem"><p>Mandant wählen</p></li><li class="listitem"><p>Druckerbeschreibung â†’ aussagekräftiger Text: wird in der
               Aufträgen oder Rechnungen als odt-Datei keine Bedeutung, darf
               aber nicht leer sein)</p></li><li class="listitem"><p>Vorlagenkürzel â†’ besr bzw. selbst gewähltes Vorlagensuffix
               (muss genau der Zeichenfolge entsprechen, die zwischen
-              "invoice_" bzw. "sales_order_" und ".odt" steht.)</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.3. Benutzereinstellungen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2462"></a>2.13.1.3. Benutzereinstellungen</h4></div></div></div><p>Wer den Ausdruck mit Einzahlungsschein als Standardeinstellung
+              "invoice_" bzw. "sales_order_" und ".odt" steht.)</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.3. Benutzereinstellungen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2456"></a>2.13.1.3. Benutzereinstellungen</h4></div></div></div><p>Wer den Ausdruck mit Einzahlungsschein als Standardeinstellung
           im Rechnungs- bzw. Auftragsformular angezeigt haben möchte, kann
           dies persönlich für sich bei den Benutzereinstellungen
           konfigurieren:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Programm â†’ Benutzereinstellungen â†’ Druckoptionen</p></li><li class="listitem"><p>Standardvorlagenformat â†’ OpenDocument/OASIS</p></li><li class="listitem"><p>Standardausgabekanal â†’ Bildschirm</p></li><li class="listitem"><p>Standarddrucker â†’ gewünschte Druckerbeschreibung auswählen
-              (z.B. mit Einzahlungsschein Bank xy)</p></li><li class="listitem"><p>Anzahl Kopien â†’ leer</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2486"></a>2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen</h4></div></div></div><p>In der Vorlage sind als Modul "BESR" 4 Makros gespeichert, die
+              (z.B. mit Einzahlungsschein Bank xy)</p></li><li class="listitem"><p>Anzahl Kopien â†’ leer</p></li><li class="listitem"><p>speichern</p></li></ul></div></div><div class="sect3" title="2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2480"></a>2.13.1.4. Aufbau und nötige Anpassungen der Vorlagen</h4></div></div></div><p>In der Vorlage sind als Modul "BESR" 4 Makros gespeichert, die
           aus dem von kivitendo erzeugten odt-Dokument die korrekte
           Referenznummer inklusive Prüfziffer sowie die Kodierzeile in
           OCRB-Schrift erzeugen und am richtigen Ort ins Dokument
               angepasst werden.</strong></span> Dabei ist darauf zu achten, dass
               sich die Positionen der Postkonto-Nummern der Bank, sowie der
               Zeichenfolgen dddfr, DDDREF1, DDDREF2, 609, DDDKODIERZEILE nicht
-              verschieben.</p></li></ul></div><div class="screenshot"><div class="mediaobject"><img src="images/Einzahlungsschein_Makro.png"></div></div></div><div class="sect3" title="2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer odt-Rechnung (analog bei Auftrag)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2550"></a>2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer
+              verschieben.</p></li></ul></div><div class="screenshot"><div class="mediaobject"><img src="images/Einzahlungsschein_Makro.png"></div></div></div><div class="sect3" title="2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer odt-Rechnung (analog bei Auftrag)"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2544"></a>2.13.1.5. Auswahl der Druckvorlage in kivitendo beim Erzeugen einer
           odt-Rechnung (analog bei Auftrag)</h4></div></div></div><p>Im Fussbereich der Rechnungsmaske muss neben Rechnung,
           OpenDocument/OASIS und Bildschirm die im Adminbereich erstellte
           Druckerbeschreibung ausgewählt werden, falls diese nicht bereits bei
           den Benutzereinstellungen als persönlicher Standard gewählt
-          wurde.</p></div><div class="sect3" title="2.13.1.6. Makroeinstellungen in LibreOffice anpassen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2555"></a>2.13.1.6. Makroeinstellungen in LibreOffice anpassen</h4></div></div></div><p>Falls beim Ã–ffnen einer von kivitendo erzeugten odt-Rechnung
+          wurde.</p></div><div class="sect3" title="2.13.1.6. Makroeinstellungen in LibreOffice anpassen"><div class="titlepage"><div><div><h4 class="title"><a name="d0e2549"></a>2.13.1.6. Makroeinstellungen in LibreOffice anpassen</h4></div></div></div><p>Falls beim Ã–ffnen einer von kivitendo erzeugten odt-Rechnung
           die Meldung kommt, dass Makros aus Sicherheitsgründen nicht
           ausgeführt werden, so müssen folgende Einstellungen in LibreOffice
           angepasst werden:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Extras â†’ Optionen â†’ Sicherheit â†’ Makrosicherheit</p></li><li class="listitem"><p>Sicherheitslevel auf "Mittel" einstellen (Diese
index 189e2c9..3c83295 100644 (file)
         bis zu welchem Monat und Jahr die aktuelle Abrechnungsperiode dauert:
         <code class="literal">Abrechnungszeitrum: &lt;%period_start_date FORMAT=%m/%Y%&gt;
         bis &lt;%period_end_date FORMAT=%m/%Y%&gt;</code>
-            </p></div><div class="sect2" title="3.1.4. Auflisten"><div class="titlepage"><div><div><h3 class="title"><a name="features.periodic-invoices.reports"></a>3.1.4. Auflisten</h3></div></div></div><p>Unter Verkauf-&gt;Berichte-&gt;Aufträge finden sich zwei neue
+            </p><p>Beim automatischen Versand der Rechnugen via E-Mail können neben diesen speziellen Variablen auch einige Eigenschaften der
+        Rechnung selber als Variablen im Betreff &amp; dem Text der E-Mails genutzt werden. Beispiele sind
+        <code class="varname">&lt;%invnumber%&gt;</code> für die Rechnungsnummber oder <code class="varname">&lt;transaction_description%&gt;</code> für die
+        Vorgangsbezeichnung. Diese Variablen stehen beim Erzeugen der Rechnung logischerweise noch nicht zur Verfügung.</p></div><div class="sect2" title="3.1.4. Auflisten"><div class="titlepage"><div><div><h3 class="title"><a name="features.periodic-invoices.reports"></a>3.1.4. Auflisten</h3></div></div></div><p>Unter Verkauf-&gt;Berichte-&gt;Aufträge finden sich zwei neue
         Checkboxen, "Wiederkehrende Rechnungen aktiv" und "Wiederkehrende
         Rechnungen inaktiv", mit denen man sich einen Ãœberglick Ã¼ber die
         wiederkehrenden Rechnungen verschaffen kann.</p></div><div class="sect2" title="3.1.5. Erzeugung der eigentlichen Rechnungen"><div class="titlepage"><div><div><h3 class="title"><a name="features.periodic-invoices.task-server"></a>3.1.5. Erzeugung der eigentlichen Rechnungen</h3></div></div></div><p>Die zeitliche und periodische Ãœberprüfung, ob eine
index fc05d06..17a457a 100644 (file)
                         <code class="varname">invdate</code>
                      </span></dt><dd><p>Rechnungsdatum</p></dd><dt><span class="term">
                         <code class="varname">invnumber</code>
-                     </span></dt><dd><p>Rechnungsnummer</p></dd></dl></div></div></div><div class="sect2" title="3.3.10. Variablen in anderen Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.andere-vorlagen"></a>3.3.10. Variablen in anderen Vorlagen</h3></div></div></div><div class="sect3" title="3.3.10.1. Einführung"><div class="titlepage"><div><div><h4 class="title"><a name="d0e5833"></a>3.3.10.1. Einführung</h4></div></div></div><p>Die Variablen in anderen Vorlagen sind Ã¤hnlich wie in der
+                     </span></dt><dd><p>Rechnungsnummer</p></dd></dl></div></div></div><div class="sect2" title="3.3.10. Variablen in anderen Vorlagen"><div class="titlepage"><div><div><h3 class="title"><a name="dokumentenvorlagen-und-variablen.andere-vorlagen"></a>3.3.10. Variablen in anderen Vorlagen</h3></div></div></div><div class="sect3" title="3.3.10.1. Einführung"><div class="titlepage"><div><div><h4 class="title"><a name="d0e5835"></a>3.3.10.1. Einführung</h4></div></div></div><p>Die Variablen in anderen Vorlagen sind Ã¤hnlich wie in der
           Rechnung. Allerdings heißen die Variablen, die mit
           <code class="varname">inv</code> beginnen, jetzt anders. Bei den Angeboten
           fangen sie mit <code class="varname">quo</code> für "quotation" an:
index 15f53f5..e94b3e3 100644 (file)
@@ -1,15 +1,15 @@
 <html><head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-   <title>3.7. Artikelklassifizierung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"><link rel="next" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.7. Artikelklassifizierung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s06.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.7. Artikelklassifizierung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.part_classification"></a>3.7. Artikelklassifizierung</h2></div></div></div><div class="sect2" title="3.7.1. Ãœbersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6515"></a>3.7.1. Ãœbersicht</h3></div></div></div><p>Die Klassifizierung von Artikeln dient einer weiteren
+   <title>3.7. Artikelklassifizierung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s06.html" title="3.6. Schweizer Kontenpläne"><link rel="next" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.7. Artikelklassifizierung</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s06.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s08.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.7. Artikelklassifizierung"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.part_classification"></a>3.7. Artikelklassifizierung</h2></div></div></div><div class="sect2" title="3.7.1. Ãœbersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6517"></a>3.7.1. Ãœbersicht</h3></div></div></div><p>Die Klassifizierung von Artikeln dient einer weiteren
         Gliederung, um zum Beispiel den Einkauf vom Verkauf zu trennen,
         gekennzeichnet durch eine Beschreibung (z.B. "Einkauf") und ein Kürzel
         (z.B. "E"). Für jede Klassifizierung besteht eine Beschreibung und
         eine Abkürzung die normalerweise aus einem Zeichen besteht, kann aber
         auf mehrere Zeichen erweitert werden, falls zur Unterscheidung
-        notwendig. Sinnvoll sind jedoch nur maximal 2 Zeichen.</p></div><div class="sect2" title="3.7.2. Basisklassifizierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6520"></a>3.7.2. Basisklassifizierung</h3></div></div></div><p>Als Basisklassifizierungen gibt es</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Einkauf</p></li><li class="listitem"><p>Verkauf</p></li><li class="listitem"><p>Handelsware</p></li><li class="listitem"><p>Produktion</p></li><li class="listitem"><p>- keine - (diese wird bei einer Aktualisierung für alle
+        notwendig. Sinnvoll sind jedoch nur maximal 2 Zeichen.</p></div><div class="sect2" title="3.7.2. Basisklassifizierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6522"></a>3.7.2. Basisklassifizierung</h3></div></div></div><p>Als Basisklassifizierungen gibt es</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Einkauf</p></li><li class="listitem"><p>Verkauf</p></li><li class="listitem"><p>Handelsware</p></li><li class="listitem"><p>Produktion</p></li><li class="listitem"><p>- keine - (diese wird bei einer Aktualisierung für alle
             existierenden Artikel verwendet und ist gültig für Verkauf und
             Einkauf)</p></li></ul></div><p>Es können weitere Klassifizierungen angelegt werden. So kann es
-        z.B. für separat auszuweisende Artikel folgende Klassen geben:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Lieferung (Logistik, Transport) mit Kürzel L</p></li><li class="listitem"><p>Material (Verpackungsmaterial) mit Kürzel M</p></li></ul></div></div><div class="sect2" title="3.7.3. Attribute"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6550"></a>3.7.3. Attribute</h3></div></div></div><p>Bisher haben die Klassifizierungen folgende Attribute, die auch
+        z.B. für separat auszuweisende Artikel folgende Klassen geben:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Lieferung (Logistik, Transport) mit Kürzel L</p></li><li class="listitem"><p>Material (Verpackungsmaterial) mit Kürzel M</p></li></ul></div></div><div class="sect2" title="3.7.3. Attribute"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6552"></a>3.7.3. Attribute</h3></div></div></div><p>Bisher haben die Klassifizierungen folgende Attribute, die auch
         alle gleichzeitg gültig sein können</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>gültig für Verkauf - dieser Artikel kann im Verkauf genutzt
             werden</p></li><li class="listitem"><p>gültig für Einkauf - dieser Artikel kann im Einkauf genutzt
             werden</p></li><li class="listitem"><p>separat ausweisen - hierzu gibt es zur Dokumentengenerierung
@@ -19,7 +19,7 @@
         pro separat auszuweisenden Klassifizierungen die Variable<span class="bold"><strong>&lt; %separate_X_subtotal%&gt;</strong></span>, wobei X das
         Kürzel der Klassifizierung ist.</p><p>Im obigen Beispiel wäre das für Lieferkosten <span class="bold"><strong>&lt;%separate_L_subtotal%&gt;</strong></span> und für
         Verpackungsmaterial <span class="bold"><strong>
-        &lt;%separate_M_subtotal%&gt;</strong></span>.</p></div><div class="sect2" title="3.7.4. Zwei-Zeichen Abkürzung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6581"></a>3.7.4. Zwei-Zeichen Abkürzung</h3></div></div></div><p>Der Typ des Artikels und die Klassifizierung werden durch zwei
+        &lt;%separate_M_subtotal%&gt;</strong></span>.</p></div><div class="sect2" title="3.7.4. Zwei-Zeichen Abkürzung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6583"></a>3.7.4. Zwei-Zeichen Abkürzung</h3></div></div></div><p>Der Typ des Artikels und die Klassifizierung werden durch zwei
         Buchstaben dargestellt. Der erste Buchstabe ist eine Lokalisierung des
         Artikel-Typs ('P','A','S'), deutsch 'W', 'E', und 'D' für Ware
         Erzeugnis oder Dienstleistung und ggf. weiterer Typen.</p><p>Der zweite Buchstabe (und ggf. auch ein dritter, falls nötig)
index e938f7d..7407e46 100644 (file)
@@ -1,10 +1,10 @@
 <html><head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-   <title>3.8. Dateiverwaltung (Mini-DMS)</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s07.html" title="3.7. Artikelklassifizierung"><link rel="next" href="ch03s09.html" title="3.9. Webshop-Api"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.8. Dateiverwaltung (Mini-DMS)</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s07.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.8. Dateiverwaltung (Mini-DMS)"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.file_managment"></a>3.8. Dateiverwaltung (Mini-DMS)</h2></div></div></div><div class="sect2" title="3.8.1. Ãœbersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6593"></a>3.8.1. Ãœbersicht</h3></div></div></div><p>Parallel zum alten WebDAV gibt es ein Datei-Management-System,
+   <title>3.8. Dateiverwaltung (Mini-DMS)</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s07.html" title="3.7. Artikelklassifizierung"><link rel="next" href="ch03s09.html" title="3.9. Webshop-Api"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.8. Dateiverwaltung (Mini-DMS)</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s07.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s09.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.8. Dateiverwaltung (Mini-DMS)"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="features.file_managment"></a>3.8. Dateiverwaltung (Mini-DMS)</h2></div></div></div><div class="sect2" title="3.8.1. Ãœbersicht"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6595"></a>3.8.1. Ãœbersicht</h3></div></div></div><p>Parallel zum alten WebDAV gibt es ein Datei-Management-System,
         das Dateien verschiedenen Typs verwaltet. Dies können</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>aus ERP-Daten per LaTeX Template erzeugte
             PDF-Dokumente,</p></li><li class="listitem"><p>zu bestimmten ERP-Daten gehörende Anhangdateien
             unterschiedlichen Formats,</p></li><li class="listitem"><p>per Scanner eingelesene PDF-Dateien,</p></li><li class="listitem"><p>per E-Mail empfangene Dateianhänge unterschiedlichen
-            Formats,</p></li><li class="listitem"><p>sowie speziel für Artikel hochgeladene Bilder sein.</p></li></ol></div><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Overview.png"></div></div></div><div class="sect2" title="3.8.2. Struktur"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6620"></a>3.8.2. Struktur</h3></div></div></div><p>Ãœber eine vom Speichermedium unabhängige Zwischenschicht werden
+            Formats,</p></li><li class="listitem"><p>sowie speziel für Artikel hochgeladene Bilder sein.</p></li></ol></div><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Overview.png"></div></div></div><div class="sect2" title="3.8.2. Struktur"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6622"></a>3.8.2. Struktur</h3></div></div></div><p>Ãœber eine vom Speichermedium unabhängige Zwischenschicht werden
         die Dateien und ihre Versionen in der Datenbank verwaltet. Darunter
         können verschiedene Implementierungen (Backends) gleichzeitig
         existieren:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Dateisystem</p></li><li class="listitem"><p>WebDAV</p></li><li class="listitem"><p>Schnittstelle zu externen
@@ -23,7 +23,7 @@
         für "attachment" und "image" nur die Quelle "uploaded". Für "document"
         gibt es auf jeden Fall die Quelle "created". Die Quellen "scanner" und
         "email" müssen derzeit in der Datenbank konfiguriert werden (siehe
-        <a class="xref" href="ch03s08.html#file_management.dbconfig" title="3.8.4.2. Datenbank-Konfigurierung">Datenbank-Konfigurierung</a>).</p></div><div class="sect2" title="3.8.3. Anwendung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6672"></a>3.8.3. Anwendung</h3></div></div></div><p>Die Daten werden bei den ERP-Objekten als extra Reiter
+        <a class="xref" href="ch03s08.html#file_management.dbconfig" title="3.8.4.2. Datenbank-Konfigurierung">Datenbank-Konfigurierung</a>).</p></div><div class="sect2" title="3.8.3. Anwendung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6674"></a>3.8.3. Anwendung</h3></div></div></div><p>Die Daten werden bei den ERP-Objekten als extra Reiter
         dargestellt. Eine Verkaufsrechnung z.B. hat die Reiter "Dokumente" und
         "Dateianhänge".</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Anhaenge.png"></div></div><p>Bei den Dateianhängen wird immer nur die aktuelle Version einer
         Datei angezeigt. Wird eine Datei mit gleichem Namen hochgeladen, so
         so sind diese z.B. bei Einkaufsrechnungen sichtbar:</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Dokumente-Scanner.png"></div></div><p>Statt des Löschens wird hier die Datei zurück zur Quelle
         verschoben. Somit kann die Datei anschließend an ein anderes
         ERP-Objekt angehängt werden.</p><p>Derzeit sind "Titel" und "Beschreibung" noch nicht genutzt. Sie
-        sind bisher nur bei Bildern relevant.</p></div><div class="sect2" title="3.8.4. Konfigurierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6715"></a>3.8.4. Konfigurierung</h3></div></div></div><div class="sect3" title="3.8.4.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="file_management.clientconfig"></a>3.8.4.1. Mandantenkonfiguration</h4></div></div></div><div class="sect4" title="3.8.4.1.1. Reiter &#34;Features&#34;"><div class="titlepage"><div><div><h5 class="title"><a name="d0e6721"></a>3.8.4.1.1. Reiter "Features"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Features</strong></span>
+        sind bisher nur bei Bildern relevant.</p></div><div class="sect2" title="3.8.4. Konfigurierung"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6717"></a>3.8.4. Konfigurierung</h3></div></div></div><div class="sect3" title="3.8.4.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="file_management.clientconfig"></a>3.8.4.1. Mandantenkonfiguration</h4></div></div></div><div class="sect4" title="3.8.4.1.1. Reiter &#34;Features&#34;"><div class="titlepage"><div><div><h5 class="title"><a name="d0e6723"></a>3.8.4.1.1. Reiter "Features"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Features</strong></span>
             im Abschnitt Dateimanagement ist neben dem "alten" WebDAV das
             Dateimangement generell zu- und abschaltbar, sowie die Zuordnung
             der Dateitypen zu Backends. Die Löschbarkeit von Dateien, sowie
             die maximale Uploadgröße sind Backend-unabhängig</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-ClientConfig.png"></div></div><p>Die einzelnen Backends sind einzeln einschaltbar.
             Spezifische Backend-Konfigurierungen sind hier noch
-            ergänzbar.</p></div><div class="sect4" title="3.8.4.1.2. Reiter &#34;Allgemeine Dokumentenanhänge&#34;"><div class="titlepage"><div><div><h5 class="title"><a name="d0e6737"></a>3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Allgemeine
+            ergänzbar.</p></div><div class="sect4" title="3.8.4.1.2. Reiter &#34;Allgemeine Dokumentenanhänge&#34;"><div class="titlepage"><div><div><h5 class="title"><a name="d0e6739"></a>3.8.4.1.2. Reiter "Allgemeine Dokumentenanhänge"</h5></div></div></div><p>Unter dem Reiter <span class="bold"><strong>Allgemeine
             Dokumentenanhänge</strong></span> kann für alle ERP-Dokumente (
             Angebote, Aufträge, Lieferscheine, Rechnungen im Verkauf und
             Einkauf ) allgemeingültige Anhänge hochgeladen werden.</p><div class="screenshot"><div class="mediaobject"><img src="images/DMS-Allgemeine-Dokumentenanhaenge.png"></div></div><p>Diese Anhänge werden beim Generieren von PDF-Dateien an die
index e58e3b1..9dd90a5 100644 (file)
@@ -1,13 +1,13 @@
 <html><head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-   <title>3.9. Webshop-Api</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"><link rel="next" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.9. Webshop-Api</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s10.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.9. Webshop-Api"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e6771"></a>3.9. Webshop-Api</h2></div></div></div><p>Das Shopmodul bietet die Möglichkeit Onlineshopartikel und
+   <title>3.9. Webshop-Api</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="ch03.html" title="Kapitel 3. Features und Funktionen"><link rel="prev" href="ch03s08.html" title="3.8. Dateiverwaltung (Mini-DMS)"><link rel="next" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3.9. Webshop-Api</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s08.html">Zurück</a>&nbsp;</td><th width="60%" align="center">Kapitel 3. Features und Funktionen</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch03s10.html">Weiter</a></td></tr></table><hr></div><div class="sect1" title="3.9. Webshop-Api"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="d0e6773"></a>3.9. Webshop-Api</h2></div></div></div><p>Das Shopmodul bietet die Möglichkeit Onlineshopartikel und
       Onlineshopbestellungen zu verwalten und zu bearbeiten.</p><p>Es ist Multishopfähig, d.h. Artikel können mehreren oder
       unterschiedlichen Shops zugeordnet werden. Bestellungen können aus
       mehreren Shops geholt werden.</p><p>Zur Zeit bietet das Modul nur einen Connector zur REST-Api von
       Shopware. Weitere Connectoren können dazu programmiert und eingerichtet
-      werden.</p><div class="sect2" title="3.9.1. Rechte für die Webshopapi"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6780"></a>3.9.1. Rechte für die Webshopapi</h3></div></div></div><p>In der Administration können folgende Rechte vergeben
-        werden</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Webshopartikel anlegen und bearbeiten</p></li><li class="listitem"><p>Shopbestellungen holen und bearbeiten</p></li><li class="listitem"><p>Shop anlegen und bearbeiten</p></li></ul></div></div><div class="sect2" title="3.9.2. Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6795"></a>3.9.2. Konfiguration</h3></div></div></div><p>Unter System-&gt;Webshops können Shops angelegt und konfiguriert
-        werden</p><div class="mediaobject"><img src="images/Shop_Listing.png"></div></div><div class="sect2" title="3.9.3. Webshopartikel"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6803"></a>3.9.3. Webshopartikel</h3></div></div></div><div class="sect3" title="3.9.3.1. Shopvariablenreiter in Artikelstammdaten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6806"></a>3.9.3.1. Shopvariablenreiter in Artikelstammdaten</h4></div></div></div><p>Mit dem Recht "Shopartikel anlegen und bearbeiten" und des
+      werden.</p><div class="sect2" title="3.9.1. Rechte für die Webshopapi"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6782"></a>3.9.1. Rechte für die Webshopapi</h3></div></div></div><p>In der Administration können folgende Rechte vergeben
+        werden</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Webshopartikel anlegen und bearbeiten</p></li><li class="listitem"><p>Shopbestellungen holen und bearbeiten</p></li><li class="listitem"><p>Shop anlegen und bearbeiten</p></li></ul></div></div><div class="sect2" title="3.9.2. Konfiguration"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6797"></a>3.9.2. Konfiguration</h3></div></div></div><p>Unter System-&gt;Webshops können Shops angelegt und konfiguriert
+        werden</p><div class="mediaobject"><img src="images/Shop_Listing.png"></div></div><div class="sect2" title="3.9.3. Webshopartikel"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6805"></a>3.9.3. Webshopartikel</h3></div></div></div><div class="sect3" title="3.9.3.1. Shopvariablenreiter in Artikelstammdaten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6808"></a>3.9.3.1. Shopvariablenreiter in Artikelstammdaten</h4></div></div></div><p>Mit dem Recht "Shopartikel anlegen und bearbeiten" und des
           Markers <span class="bold"><strong>"Shopartikel" in den Basisdaten
           </strong></span>zeigt sich der Reiter "Shopvariablen" in den
           Artikelstammdaten. Hier können jetzt die Artikel mit
           Stelle können auch beliebig viele Bilder dem Shopartikel zugeordnet
           werden. Artikelbilder gelten für alle Shops.</p><div class="mediaobject"><img src="images/Shop_Artikel.png"></div><p>Die Artikelgruppen werden direkt vom Shopsystem geholt somit
           ist es möglich einen Artikel auch mehreren Gruppen
-          zuzuordenen</p></div><div class="sect3" title="3.9.3.2. Shopartikelliste"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6819"></a>3.9.3.2. Shopartikelliste</h4></div></div></div><p>Unter dem Menu Webshop-&gt;Webshop Artikel hat man nochmal
+          zuzuordenen</p></div><div class="sect3" title="3.9.3.2. Shopartikelliste"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6821"></a>3.9.3.2. Shopartikelliste</h4></div></div></div><p>Unter dem Menu Webshop-&gt;Webshop Artikel hat man nochmal
           eine Gesamtübersicht. Von hier aus ist es möglich Artikel im Stapel
           unter verschiedenen Kriterien &lt;alles&gt;&lt;nur Preis&gt;&lt;nur
           Bestand&gt;&lt;Preis und Bestand&gt; an die jeweiligen Shops
-          hochzuladen.</p><div class="mediaobject"><img src="images/Shop_Artikel_Listing.png"></div></div></div><div class="sect2" title="3.9.4. Bestellimport"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6827"></a>3.9.4. Bestellimport</h3></div></div></div><p>Unter dem Menupunkt Webshop-&gt;Webshop Import Ã¶ffnet sich die
+          hochzuladen.</p><div class="mediaobject"><img src="images/Shop_Artikel_Listing.png"></div></div></div><div class="sect2" title="3.9.4. Bestellimport"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6829"></a>3.9.4. Bestellimport</h3></div></div></div><p>Unter dem Menupunkt Webshop-&gt;Webshop Import Ã¶ffnet sich die
         Bestellimportsliste. Hier ist sind Möglichkeiten gegeben Neue
         Bestellungen vom Shop abzuholen, geholte Bestellungen im Stapel oder
         einzeln als Auftrag zu transferieren. Die Liste kann nach
@@ -52,7 +52,7 @@
             auch der Grund für die Auftragssperre sein.</p></li><li class="listitem"><p>Die Buttons "Auftrag erstellen" und "Kunde mit
             Rechnungsadresse Ã¼berschreiben" zeigen sich erst, wenn ein Kunde
             aus dem Listing ausgewählt ist.</p></li><li class="listitem"><p>Es ist aber möglich die Shopbestellung zu löschen.</p></li><li class="listitem"><p>Ist eine Bestellung schon Ã¼bernommen, zeigen sich an dieser
-            Stelle, die dazugehörigen Belegverknüpfungen.</p></li></ul></div></div><div class="sect2" title="3.9.5. Mapping der Daten"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6880"></a>3.9.5. Mapping der Daten</h3></div></div></div><p>Das Mapping der kivitendo Daten mit den Shopdaten geschieht in
+            Stelle, die dazugehörigen Belegverknüpfungen.</p></li></ul></div></div><div class="sect2" title="3.9.5. Mapping der Daten"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6882"></a>3.9.5. Mapping der Daten</h3></div></div></div><p>Das Mapping der kivitendo Daten mit den Shopdaten geschieht in
         der Datei SL/ShopConnector/&lt;SHOPCONNECTORNAME&gt;.pm
         z.B.:SL/ShopConnector/Shopware.pm</p><p>In dieser Datei gibt es einen Bereich wo die Bestellpostionen,
         die Bestellkopfdaten und die Artikeldaten gemapt werden. In dieser
index e3aaa24..c6e155b 100644 (file)
             </p></div><div class="sect2" title="3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.create_zugferd_bills"></a>3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo</h3></div></div></div><p>Für die Erstellung von ZUGFeRD Rechnungen bedarf es in
                        kivitendo zwei Dinge:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>Die Erstellung muss in der Mandantenkonfiguration
                                        aktiviert sein</p></li><li class="listitem"><p>Beim mindestens einem Bankkonto muss die Option
-                                       â€žNutzung von ZUGFeRD“ aktiviert sein</p></li></ol></div><div class="sect3" title="3.10.3.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6927"></a>3.10.3.1. Mandantenkonfiguration</h4></div></div></div><p>Die Einstellung für die Erstellung von ZUGFeRD Rechnungen
+                                       â€žNutzung von ZUGFeRD“ aktiviert sein</p></li></ol></div><div class="sect3" title="3.10.3.1. Mandantenkonfiguration"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6929"></a>3.10.3.1. Mandantenkonfiguration</h4></div></div></div><p>Die Einstellung für die Erstellung von ZUGFeRD Rechnungen
                                erfolgt unter â€žSystem“ â†’ â€žMandatenkonfiguration“ â†’ â€žFeatures“.
                                Im Abschnitt â€žEinkauf und Verkauf“ finden Sie die Einstellung
                                â€žVerkaufsrechnungen mit ZUGFeRD-Daten erzeugen“.
                                Hier besteht die Auswahl zwischen:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>ZUGFeRD-Rechnungen erzeugen</p></li><li class="listitem"><p>ZUGFeRD-Rechnungen im Testmodus erzeugen</p></li><li class="listitem"><p>Keine ZUGFeRD Rechnungen erzeugen</p></li></ul></div><p>Rechnungen die als PDF erzeugt werden, werden je nach
-                               Einstellung nun im ZUGFeRD Format ausgegeben.</p></div><div class="sect3" title="3.10.3.2. Konfiguration der Bankkonten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6944"></a>3.10.3.2. Konfiguration der Bankkonten</h4></div></div></div><p>Unter â€žSystem â†’ Bankkonten“ muss bei mindestens einem
+                               Einstellung nun im ZUGFeRD Format ausgegeben.</p></div><div class="sect3" title="3.10.3.2. Konfiguration der Bankkonten"><div class="titlepage"><div><div><h4 class="title"><a name="d0e6946"></a>3.10.3.2. Konfiguration der Bankkonten</h4></div></div></div><p>Unter â€žSystem â†’ Bankkonten“ muss bei mindestens einem
                                Bankkonto die Option â€žNutzung mit ZUGFeRD“ auf â€žJa“ gestellt
                                werden.</p></div></div><div class="sect2" title="3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo"><div class="titlepage"><div><div><h3 class="title"><a name="features.zugferd.read_zugferd_bills"></a>3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo</h3></div></div></div><p>Es lassen sich auch Rechnungen von Kreditoren, die im
                        ZUGFeRD Format erstellt wurden, nach Kivitendo importieren.
index f78a81c..ebea68e 100644 (file)
@@ -1,6 +1,6 @@
 <html><head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-   <title>Kapitel 4. Entwicklerdokumentation</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"><link rel="next" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 4. Entwicklerdokumentation</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s10.html">Zurück</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch04s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 4. Entwicklerdokumentation"><div class="titlepage"><div><div><h2 class="title"><a name="d0e6963"></a>Kapitel 4. Entwicklerdokumentation</h2></div></div></div><div class="sect1" title="4.1. Globale Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.globals"></a>4.1. Globale Variablen</h2></div></div></div><div class="sect2" title="4.1.1. Wie sehen globale Variablen in Perl aus?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6969"></a>4.1.1. Wie sehen globale Variablen in Perl aus?</h3></div></div></div><p>Globale Variablen liegen in einem speziellen namespace namens
+   <title>Kapitel 4. Entwicklerdokumentation</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="up" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="prev" href="ch03s10.html" title="3.10. ZUGFeRD Rechnungen"><link rel="next" href="ch04s02.html" title="4.2. Entwicklung unter FastCGI"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Kapitel 4. Entwicklerdokumentation</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s10.html">Zurück</a>&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch04s02.html">Weiter</a></td></tr></table><hr></div><div class="chapter" title="Kapitel 4. Entwicklerdokumentation"><div class="titlepage"><div><div><h2 class="title"><a name="d0e6965"></a>Kapitel 4. Entwicklerdokumentation</h2></div></div></div><div class="sect1" title="4.1. Globale Variablen"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="devel.globals"></a>4.1. Globale Variablen</h2></div></div></div><div class="sect2" title="4.1.1. Wie sehen globale Variablen in Perl aus?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e6971"></a>4.1.1. Wie sehen globale Variablen in Perl aus?</h3></div></div></div><p>Globale Variablen liegen in einem speziellen namespace namens
         "main", der von Ã¼berall erreichbar ist. Darüber hinaus sind bareword
         globs global und die meisten speziellen Variablen sind...
         speziell.</p><p>Daraus ergeben sich folgende Formen:</p><div class="variablelist"><dl><dt><span class="term">
@@ -25,7 +25,7 @@
               <code class="varname">$PACKAGE::form</code>.</p></dd><dt><span class="term">
                      <code class="literal">local $form</code>
                   </span></dt><dd><p>Alle Ã„nderungen an <code class="varname">$form</code> werden am Ende
-              des scopes zurückgesetzt</p></dd></dl></div></div><div class="sect2" title="4.1.2. Warum sind globale Variablen ein Problem?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7070"></a>4.1.2. Warum sind globale Variablen ein Problem?</h3></div></div></div><p>Das erste Problem ist <span class="productname">FCGI</span>â„¢.</p><p>
+              des scopes zurückgesetzt</p></dd></dl></div></div><div class="sect2" title="4.1.2. Warum sind globale Variablen ein Problem?"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7072"></a>4.1.2. Warum sind globale Variablen ein Problem?</h3></div></div></div><p>Das erste Problem ist <span class="productname">FCGI</span>â„¢.</p><p>
                <span class="productname">SQL-Ledger</span>â„¢ hat fast alles im globalen
         namespace abgelegt, und erwartet, dass es da auch wiederzufinden ist.
         Unter <span class="productname">FCGI</span>â„¢ müssen diese Sachen aber wieder
@@ -39,7 +39,7 @@
         dies hat, seit der Einführung, u.a. schon so manche langwierige
         Bug-Suche verkürzt. Da globale Variablen aber implizit mit Package
         angegeben werden, werden die nicht geprüft, und somit kann sich
-        schnell ein Tippfehler einschleichen.</p></div><div class="sect2" title="4.1.3. Kanonische globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7103"></a>4.1.3. Kanonische globale Variablen</h3></div></div></div><p>Um dieses Problem im Griff zu halten gibt es einige wenige
+        schnell ein Tippfehler einschleichen.</p></div><div class="sect2" title="4.1.3. Kanonische globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7105"></a>4.1.3. Kanonische globale Variablen</h3></div></div></div><p>Um dieses Problem im Griff zu halten gibt es einige wenige
         globale Variablen, die kanonisch sind, d.h. sie haben bestimmte
         vorgegebenen Eigenschaften, und alles andere sollte anderweitig
         umhergereicht werden.</p><p>Diese Variablen sind im Moment die folgenden neun:</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
@@ -62,7 +62,7 @@
                      <code class="varname">$::request</code>
                   </p></li></ul></div><p>Damit diese nicht erneut als Müllhalde missbraucht werden, im
         Folgenden eine kurze Erläuterung der bestimmten vorgegebenen
-        Eigenschaften (Konventionen):</p><div class="sect3" title="4.1.3.1. $::form"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7167"></a>4.1.3.1. $::form</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Ist ein Objekt der Klasse
+        Eigenschaften (Konventionen):</p><div class="sect3" title="4.1.3.1. $::form"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7169"></a>4.1.3.1. $::form</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Ist ein Objekt der Klasse
               "<code class="classname">Form</code>"</p></li><li class="listitem"><p>Wird nach jedem Request gelöscht</p></li><li class="listitem"><p>Muss auch in Tests und Konsolenscripts vorhanden
               sein.</p></li><li class="listitem"><p>Enthält am Anfang eines Requests die Requestparameter vom
               User</p></li><li class="listitem"><p>Kann zwar intern Ã¼ber Requestgrenzen ein Datenbankhandle
   push @{ $form-&gt;{TEMPLATE_ARRAYS}{number} },          $form-&gt;{"partnumber_$i"};
   push @{ $form-&gt;{TEMPLATE_ARRAYS}{description} },     $form-&gt;{"description_$i"};
   # ...
-}</pre></div><div class="sect3" title="4.1.3.2. %::myconfig"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7251"></a>4.1.3.2. %::myconfig</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Das einzige Hash unter den globalen Variablen</p></li><li class="listitem"><p>Wird spätestens benötigt wenn auf die Datenbank
+}</pre></div><div class="sect3" title="4.1.3.2. %::myconfig"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7253"></a>4.1.3.2. %::myconfig</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Das einzige Hash unter den globalen Variablen</p></li><li class="listitem"><p>Wird spätestens benötigt wenn auf die Datenbank
               zugegriffen wird</p></li><li class="listitem"><p>Wird bei jedem Request neu erstellt.</p></li><li class="listitem"><p>Enthält die Userdaten des aktuellen Logins</p></li><li class="listitem"><p>Sollte nicht ohne Filterung irgendwo gedumpt werden oder
               extern serialisiert werden, weil da auch der Datenbankzugriff
               für diesen user drinsteht.</p></li><li class="listitem"><p>Enthält unter anderem Datumsformat dateformat und
           Ã¼berwiegend die Daten, die sich unter <span class="guimenu">Programm</span>
           -&gt; <span class="guimenuitem">Einstellungen</span> befinden, bzw. die
           Informationen Ã¼ber den Benutzer die Ã¼ber die
-          Administrator-Schnittstelle eingegeben wurden.</p></div><div class="sect3" title="4.1.3.3. $::locale"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7290"></a>4.1.3.3. $::locale</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "Locale"</p></li><li class="listitem"><p>Wird pro Request erstellt</p></li><li class="listitem"><p>Muss auch für Tests und Scripte immer verfügbar
+          Administrator-Schnittstelle eingegeben wurden.</p></div><div class="sect3" title="4.1.3.3. $::locale"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7292"></a>4.1.3.3. $::locale</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "Locale"</p></li><li class="listitem"><p>Wird pro Request erstellt</p></li><li class="listitem"><p>Muss auch für Tests und Scripte immer verfügbar
               sein.</p></li><li class="listitem"><p>Cached intern Ã¼ber Requestgrenzen hinweg benutzte
               Locales</p></li></ul></div><p>Lokalisierung für den aktuellen User. Alle Ãœbersetzungen,
-          Zahlen- und Datumsformatierungen laufen Ã¼ber dieses Objekt.</p></div><div class="sect3" title="4.1.3.4. $::lxdebug"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7308"></a>4.1.3.4. $::lxdebug</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "LXDebug"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Muss immer verfügbar sein, in nahezu allen
+          Zahlen- und Datumsformatierungen laufen Ã¼ber dieses Objekt.</p></div><div class="sect3" title="4.1.3.4. $::lxdebug"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7310"></a>4.1.3.4. $::lxdebug</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "LXDebug"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Muss immer verfügbar sein, in nahezu allen
               Funktionen</p></li></ul></div><p>
                   <code class="varname">$::lxdebug</code> stellt Debuggingfunktionen
           bereit, wie "<code class="function">enter_sub</code>" und
           "<code class="function">message</code>" und "<code class="function">dump</code>" mit
           denen man flott Informationen ins Log (tmp/kivitendo-debug.log)
           packen kann.</p><p>Beispielsweise so:</p><pre class="programlisting">$main::lxdebug-&gt;message(0, 'Meine Konfig:' . Dumper (%::myconfig));
-$main::lxdebug-&gt;message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form-&gt;{vc});</pre></div><div class="sect3" title="4.1.3.5. $::auth"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7345"></a>4.1.3.5. $::auth</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "SL::Auth"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Hat eine permanente DB Verbindung zur Authdatenbank</p></li><li class="listitem"><p>Wird nach jedem Request resettet.</p></li></ul></div><p>
+$main::lxdebug-&gt;message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form-&gt;{vc});</pre></div><div class="sect3" title="4.1.3.5. $::auth"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7347"></a>4.1.3.5. $::auth</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse "SL::Auth"</p></li><li class="listitem"><p>Wird global gecached</p></li><li class="listitem"><p>Hat eine permanente DB Verbindung zur Authdatenbank</p></li><li class="listitem"><p>Wird nach jedem Request resettet.</p></li></ul></div><p>
                   <code class="varname">$::auth</code> stellt Funktionen bereit um die
           Rechte des aktuellen Users abzufragen. Obwohl diese Informationen
           vom aktuellen User abhängen wird das Objekt aus
@@ -144,7 +144,7 @@ $main::lxdebug-&gt;message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form-&gt;{
           Dessen Einstellungen können Ã¼ber
           <code class="literal">$::auth-&gt;client</code> abgefragt werden; Rückgabewert
           ist ein Hash mit den Werten aus der Tabelle
-          <code class="literal">auth.clients</code>.</p></div><div class="sect3" title="4.1.3.6. $::lx_office_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7374"></a>4.1.3.6. $::lx_office_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+          <code class="literal">auth.clients</code>.</p></div><div class="sect3" title="4.1.3.6. $::lx_office_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7376"></a>4.1.3.6. $::lx_office_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
               "<code class="classname">SL::LxOfficeConf</code>"</p></li><li class="listitem"><p>Global gecached</p></li><li class="listitem"><p>Repräsentation der
               <code class="filename">config/kivitendo.conf[.default]</code>-Dateien</p></li></ul></div><p>Globale Konfiguration. Configdateien werden zum Start gelesen
           und danach nicht mehr angefasst. Es ist derzeit nicht geplant, dass
@@ -154,16 +154,16 @@ $main::lxdebug-&gt;message(0, 'Wer bin ich? Kunde oder Lieferant:' . $form-&gt;{
 file_name = /tmp/kivitendo-debug.log</pre><p>ist der Key <code class="varname">file</code> im Programm als
           <code class="varname">$::lx_office_conf-&gt;{debug}{file}</code>
           erreichbar.</p><div class="warning" title="Warnung" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Warning"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Warnung]" src="system/docbook-xsl/images/warning.png"></td><th align="left">Warnung</th></tr><tr><td align="left" valign="top"><p>Zugriff auf die Konfiguration erfolgt im Moment Ã¼ber
-            Hashkeys, sind also nicht gegen Tippfehler abgesichert.</p></td></tr></table></div></div><div class="sect3" title="4.1.3.7. $::instance_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7410"></a>4.1.3.7. $::instance_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+            Hashkeys, sind also nicht gegen Tippfehler abgesichert.</p></td></tr></table></div></div><div class="sect3" title="4.1.3.7. $::instance_conf"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7412"></a>4.1.3.7. $::instance_conf</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
               "<code class="classname">SL::InstanceConfiguration</code>"</p></li><li class="listitem"><p>wird pro Request neu erstellt</p></li></ul></div><p>Funktioniert wie <code class="varname">$::lx_office_conf</code>,
           speichert aber Daten die von der Instanz abhängig sind. Eine Instanz
           ist hier eine Mandantendatenbank. Beispielsweise Ã¼berprüft
           </p><pre class="programlisting">$::instance_conf-&gt;get_inventory_system eq 'perpetual'</pre><p>
-          ob die berüchtigte Bestandsmethode zur Anwendung kommt.</p></div><div class="sect3" title="4.1.3.8. $::dispatcher"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7431"></a>4.1.3.8. $::dispatcher</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
+          ob die berüchtigte Bestandsmethode zur Anwendung kommt.</p></div><div class="sect3" title="4.1.3.8. $::dispatcher"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7433"></a>4.1.3.8. $::dispatcher</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Objekt der Klasse
               "<code class="varname">SL::Dispatcher</code>"</p></li><li class="listitem"><p>wird pro Serverprozess erstellt.</p></li><li class="listitem"><p>enthält Informationen Ã¼ber die technische Verbindung zum
               Server</p></li></ul></div><p>Der dritte Punkt ist auch der einzige Grund warum das Objekt
           global gespeichert wird. Wird vermutlich irgendwann in einem anderen
-          Objekt untergebracht.</p></div><div class="sect3" title="4.1.3.9. $::request"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7449"></a>4.1.3.9. $::request</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Hashref (evtl später Objekt)</p></li><li class="listitem"><p>Wird pro Request neu initialisiert.</p></li><li class="listitem"><p>Keine Unterstruktur garantiert.</p></li></ul></div><p>
+          Objekt untergebracht.</p></div><div class="sect3" title="4.1.3.9. $::request"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7451"></a>4.1.3.9. $::request</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>Hashref (evtl später Objekt)</p></li><li class="listitem"><p>Wird pro Request neu initialisiert.</p></li><li class="listitem"><p>Keine Unterstruktur garantiert.</p></li></ul></div><p>
                   <code class="varname">$::request</code> ist ein generischer Platz um
           Daten "für den aktuellen Request" abzulegen. Sollte nicht für action
           at a distance benutzt werden, sondern um lokales memoizing zu
@@ -176,20 +176,20 @@ file_name = /tmp/kivitendo-debug.log</pre><p>ist der Key <code class="varname">f
               <code class="varname">$::request</code>
                      </p></li><li class="listitem"><p>Muss ich von anderen Teilen des Programms lesend drauf
               zugreifen? Dann <code class="varname">$::request</code>, aber Zugriff Ã¼ber
-              Wrappermethode</p></li></ul></div></div></div><div class="sect2" title="4.1.4. Ehemalige globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7491"></a>4.1.4. Ehemalige globale Variablen</h3></div></div></div><p>Die folgenden Variablen waren einmal im Programm, und wurden
-        entfernt.</p><div class="sect3" title="4.1.4.1. $::cgi"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7496"></a>4.1.4.1. $::cgi</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil cookie Methoden nicht als
+              Wrappermethode</p></li></ul></div></div></div><div class="sect2" title="4.1.4. Ehemalige globale Variablen"><div class="titlepage"><div><div><h3 class="title"><a name="d0e7493"></a>4.1.4. Ehemalige globale Variablen</h3></div></div></div><p>Die folgenden Variablen waren einmal im Programm, und wurden
+        entfernt.</p><div class="sect3" title="4.1.4.1. $::cgi"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7498"></a>4.1.4.1. $::cgi</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil cookie Methoden nicht als
               Klassenfunktionen funktionieren</p></li><li class="listitem"><p>Aufruf als Klasse erzeugt Dummyobjekt was im
               Klassennamespace gehalten wird und Ã¼ber Requestgrenzen
               leaked</p></li><li class="listitem"><p>liegt jetzt unter
               <code class="varname">$::request-&gt;{cgi}</code>
-                     </p></li></ul></div></div><div class="sect3" title="4.1.4.2. $::all_units"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7512"></a>4.1.4.2. $::all_units</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil einige Funktionen in Schleifen zum Teil
+                     </p></li></ul></div></div><div class="sect3" title="4.1.4.2. $::all_units"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7514"></a>4.1.4.2. $::all_units</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>war nötig, weil einige Funktionen in Schleifen zum Teil
               ein paar hundert mal pro Request eine Liste der Einheiten
               brauchen, und de als Parameter durch einen Riesenstack von
               Funktionen geschleift werden müssten.</p></li><li class="listitem"><p>Liegt jetzt unter
               <code class="varname">$::request-&gt;{cache}{all_units}</code>
                      </p></li><li class="listitem"><p>Wird nur in
               <code class="function">AM-&gt;retrieve_all_units()</code> gesetzt oder
-              gelesen.</p></li></ul></div></div><div class="sect3" title="4.1.4.3. %::called_subs"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7531"></a>4.1.4.3. %::called_subs</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>wurde benutzt um callsub deep recursions
+              gelesen.</p></li></ul></div></div><div class="sect3" title="4.1.4.3. %::called_subs"><div class="titlepage"><div><div><h4 class="title"><a name="d0e7533"></a>4.1.4.3. %::called_subs</h4></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>wurde benutzt um callsub deep recursions
               abzufangen.</p></li><li class="listitem"><p>Wurde entfernt, weil callsub nur einen Bruchteil der
               möglichen Rekursioenen darstellt, und da nie welche
               auftreten.</p></li><li class="listitem"><p>komplette recursion protection wurde entfernt.</p></li></ul></div></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s10.html">Zurück</a>&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="ch04s02.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">3.10. ZUGFeRD Rechnungen&nbsp;</td><td width="20%" align="center"><a accesskey="h" href="index.html">Zum Anfang</a></td><td width="40%" align="right" valign="top">&nbsp;4.2. Entwicklung unter FastCGI</td></tr></table></div></body></html>
\ No newline at end of file
index 6eef76b..b405e6c 100644 (file)
@@ -2,8 +2,8 @@
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets V1.76.1-RC2"><link rel="home" href="index.html" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><link rel="next" href="ch01.html" title="Kapitel 1. Aktuelle Hinweise"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">kivitendo 3.5.6.1: Installation, Konfiguration,
   Entwicklung</th></tr><tr><td width="20%" align="left">&nbsp;</td><th width="60%" align="center">&nbsp;</th><td width="20%" align="right">&nbsp;<a accesskey="n" href="ch01.html">Weiter</a></td></tr></table><hr></div><div lang="de" class="book" title="kivitendo 3.5.6.1: Installation, Konfiguration, Entwicklung"><div class="titlepage"><div><div><h1 class="title"><a name="kivitendo-documentation"></a>kivitendo 3.5.6.1: Installation, Konfiguration,
-  Entwicklung</h1></div></div><hr></div><div class="toc"><p><b>Inhaltsverzeichnis</b></p><dl><dt><span class="chapter"><a href="ch01.html">1. Aktuelle Hinweise</a></span></dt><dt><span class="chapter"><a href="ch02.html">2. Installation und Grundkonfiguration</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch02.html#Installation-%C3%9Cbersicht">2.1. Ãœbersicht</a></span></dt><dt><span class="sect1"><a href="ch02s02.html">2.2. Benötigte Software und Pakete</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s02.html#Betriebssystem">2.2.1. Betriebssystem</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#Pakete">2.2.2. Benötigte Perl-Pakete installieren</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#d0e649">2.2.3. Andere Pakete installieren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s03.html">2.3. Manuelle Installation des Programmpaketes</a></span></dt><dt><span class="sect1"><a href="ch02s04.html">2.4. kivitendo-Konfigurationsdatei</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s04.html#config.config-file.introduction">2.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.sections-parameters">2.4.2. Abschnitte und Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.prior-versions">2.4.3. Versionen vor 2.6.3</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s05.html">2.5. Anpassung der PostgreSQL-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s05.html#Zeichens%C3%A4tze-die-Verwendung-von-UTF-8">2.5.1. Zeichensätze/die Verwendung von Unicode/UTF-8</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#%C3%84nderungen-an-Konfigurationsdateien">2.5.2. Ã„nderungen an Konfigurationsdateien</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-servergespeicherte-Prozeduren">2.5.3. Erweiterung für servergespeicherte Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-trigram">2.5.4. Erweiterung für Trigram Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Datenbankbenutzer-anlegen">2.5.5. Datenbankbenutzer anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s06.html">2.6. Webserver-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s06.html#d0e1129">2.6.1. Grundkonfiguration mittels CGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#Apache-Konfiguration.FCGI">2.6.2. Konfiguration für FastCGI/FCGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1283">2.6.3. Authentifizierung mittels HTTP Basic Authentication</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1299">2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1313">2.6.5. Weitergehende Konfiguration</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s07.html">2.7. Der Task-Server</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-des-Task-Servers">2.7.1. Verfügbare und notwendige Konfigurationsoptionen</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-der-Mandanten-fuer-den-Task-Servers">2.7.2. Konfiguration der Mandanten für den Task-Server</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Einbinden-in-den-Boot-Prozess">2.7.3. Automatisches Starten des Task-Servers beim Booten</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Prozesskontrolle">2.7.4. Wie der Task-Server gestartet und beendet wird</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Tasks konfigurieren">2.7.5. Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s08.html">2.8. Benutzerauthentifizierung und Administratorpasswort</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s08.html#Grundlagen-zur-Benutzerauthentifizierung">2.8.1. Grundlagen zur Benutzerauthentifizierung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Administratorpasswort">2.8.2. Administratorpasswort</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Authentifizierungsdatenbank">2.8.3. Authentifizierungsdatenbank</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Passwort%C3%BCberpr%C3%BCfung">2.8.4. Passwortüberprüfung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Name-des-Session-Cookies">2.8.5. Name des Session-Cookies</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Anlegen-der-Authentifizierungsdatenbank">2.8.6. Anlegen der Authentifizierungsdatenbank</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s09.html">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s09.html#Zusammenh%C3%A4nge">2.9.1. Zusammenhänge</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-Benutzer-Gruppen">2.9.2. Mandanten, Benutzer und Gruppen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Datenbanken-anlegen">2.9.3. Datenbanken anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Gruppen-anlegen">2.9.4. Gruppen anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Benutzer-anlegen">2.9.5. Benutzer anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-anlegen">2.9.6. Mandanten anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s10.html">2.10. Drucker- und Systemverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s10.html#Druckeradministration">2.10.1. Druckeradministration</a></span></dt><dt><span class="sect2"><a href="ch02s10.html#System">2.10.2. System sperren / entsperren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s11.html">2.11. E-Mail-Versand aus kivitendo heraus</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.sendmail">2.11.1. Versand Ã¼ber lokalen E-Mail-Server</a></span></dt><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.smtp">2.11.2. Versand Ã¼ber einen SMTP-Server</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s12.html">2.12. Drucken mit kivitendo</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s12.html#Vorlagenverzeichnis-anlegen">2.12.1. Vorlagenverzeichnis anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-RB">2.12.2. Der Druckvorlagensatz RB</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-rev-odt">2.12.3. Der Druckvorlagensatz rev-odt</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#allgemeine-hinweise-zu-latex">2.12.4. Allgemeine Hinweise zu LaTeX Vorlagen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s13.html">2.13. OpenDocument-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s13.html#d0e2420">2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s14.html">2.14. Nomenklatur</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s14.html#booking.dates">2.14.1. Datum bei Buchungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s15.html">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
+  Entwicklung</h1></div></div><hr></div><div class="toc"><p><b>Inhaltsverzeichnis</b></p><dl><dt><span class="chapter"><a href="ch01.html">1. Aktuelle Hinweise</a></span></dt><dt><span class="chapter"><a href="ch02.html">2. Installation und Grundkonfiguration</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch02.html#Installation-%C3%9Cbersicht">2.1. Ãœbersicht</a></span></dt><dt><span class="sect1"><a href="ch02s02.html">2.2. Benötigte Software und Pakete</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s02.html#Betriebssystem">2.2.1. Betriebssystem</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#Pakete">2.2.2. Benötigte Perl-Pakete installieren</a></span></dt><dt><span class="sect2"><a href="ch02s02.html#d0e649">2.2.3. Andere Pakete installieren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s03.html">2.3. Manuelle Installation des Programmpaketes</a></span></dt><dt><span class="sect1"><a href="ch02s04.html">2.4. kivitendo-Konfigurationsdatei</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s04.html#config.config-file.introduction">2.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.sections-parameters">2.4.2. Abschnitte und Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s04.html#config.config-file.prior-versions">2.4.3. Versionen vor 2.6.3</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s05.html">2.5. Anpassung der PostgreSQL-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s05.html#Zeichens%C3%A4tze-die-Verwendung-von-UTF-8">2.5.1. Zeichensätze/die Verwendung von Unicode/UTF-8</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#%C3%84nderungen-an-Konfigurationsdateien">2.5.2. Ã„nderungen an Konfigurationsdateien</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-servergespeicherte-Prozeduren">2.5.3. Erweiterung für servergespeicherte Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Erweiterung-f%C3%BCr-trigram">2.5.4. Erweiterung für Trigram Prozeduren</a></span></dt><dt><span class="sect2"><a href="ch02s05.html#Datenbankbenutzer-anlegen">2.5.5. Datenbankbenutzer anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s06.html">2.6. Webserver-Konfiguration</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s06.html#d0e1123">2.6.1. Grundkonfiguration mittels CGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#Apache-Konfiguration.FCGI">2.6.2. Konfiguration für FastCGI/FCGI</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1277">2.6.3. Authentifizierung mittels HTTP Basic Authentication</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1293">2.6.4. Aktivierung von mod_rewrite/directory_match für git basierte Installationen</a></span></dt><dt><span class="sect2"><a href="ch02s06.html#d0e1307">2.6.5. Weitergehende Konfiguration</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s07.html">2.7. Der Task-Server</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-des-Task-Servers">2.7.1. Verfügbare und notwendige Konfigurationsoptionen</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Konfiguration-der-Mandanten-fuer-den-Task-Servers">2.7.2. Konfiguration der Mandanten für den Task-Server</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Einbinden-in-den-Boot-Prozess">2.7.3. Automatisches Starten des Task-Servers beim Booten</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Prozesskontrolle">2.7.4. Wie der Task-Server gestartet und beendet wird</a></span></dt><dt><span class="sect2"><a href="ch02s07.html#Tasks konfigurieren">2.7.5. Exemplarische Konfiguration eines Hintergrund-Jobs, der die Jahreszahl in allen Nummernkreisen zum Jahreswechsel erhöht</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s08.html">2.8. Benutzerauthentifizierung und Administratorpasswort</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s08.html#Grundlagen-zur-Benutzerauthentifizierung">2.8.1. Grundlagen zur Benutzerauthentifizierung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Administratorpasswort">2.8.2. Administratorpasswort</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Authentifizierungsdatenbank">2.8.3. Authentifizierungsdatenbank</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Passwort%C3%BCberpr%C3%BCfung">2.8.4. Passwortüberprüfung</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Name-des-Session-Cookies">2.8.5. Name des Session-Cookies</a></span></dt><dt><span class="sect2"><a href="ch02s08.html#Anlegen-der-Authentifizierungsdatenbank">2.8.6. Anlegen der Authentifizierungsdatenbank</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s09.html">2.9. Mandanten-, Benutzer- und Gruppenverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s09.html#Zusammenh%C3%A4nge">2.9.1. Zusammenhänge</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-Benutzer-Gruppen">2.9.2. Mandanten, Benutzer und Gruppen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Datenbanken-anlegen">2.9.3. Datenbanken anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Gruppen-anlegen">2.9.4. Gruppen anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Benutzer-anlegen">2.9.5. Benutzer anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s09.html#Mandanten-anlegen">2.9.6. Mandanten anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s10.html">2.10. Drucker- und Systemverwaltung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s10.html#Druckeradministration">2.10.1. Druckeradministration</a></span></dt><dt><span class="sect2"><a href="ch02s10.html#System">2.10.2. System sperren / entsperren</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s11.html">2.11. E-Mail-Versand aus kivitendo heraus</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.sendmail">2.11.1. Versand Ã¼ber lokalen E-Mail-Server</a></span></dt><dt><span class="sect2"><a href="ch02s11.html#config.sending-email.smtp">2.11.2. Versand Ã¼ber einen SMTP-Server</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s12.html">2.12. Drucken mit kivitendo</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s12.html#Vorlagenverzeichnis-anlegen">2.12.1. Vorlagenverzeichnis anlegen</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-RB">2.12.2. Der Druckvorlagensatz RB</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#Vorlagen-rev-odt">2.12.3. Der Druckvorlagensatz rev-odt</a></span></dt><dt><span class="sect2"><a href="ch02s12.html#allgemeine-hinweise-zu-latex">2.12.4. Allgemeine Hinweise zu LaTeX Vorlagen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s13.html">2.13. OpenDocument-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s13.html#d0e2414">2.13.1. OpenDocument (odt) Druckvorlagen mit Makros</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s14.html">2.14. Nomenklatur</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s14.html#booking.dates">2.14.1. Datum bei Buchungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s15.html">2.15. Konfiguration zur Einnahmenüberschussrechnung/Bilanzierung:
       EUR</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s15.html#config.eur.introduction">2.15.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.parameters">2.15.2. Konfigurationsparameter</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.setting-parameters">2.15.3. Festlegen der Parameter</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.inventory-system-perpetual">2.15.4. Bemerkungen zur Bestandsmethode</a></span></dt><dt><span class="sect2"><a href="ch02s15.html#config.eur.knonw-issues">2.15.5. Bekannte Probleme</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s16.html">2.16. SKR04 19% Umstellung für innergemeinschaftlichen Erwerb</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch02s16.html#config.skr04-update-3804.introduction">2.16.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch02s16.html#config.skr04-update-3804.create-chart">2.16.2. Konto 3804 manuell anlegen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch02s17.html">2.17. Verhalten des Bilanzberichts</a></span></dt><dt><span class="sect1"><a href="ch02s18.html">2.18. Erfolgsrechnung</a></span></dt><dt><span class="sect1"><a href="ch02s19.html">2.19. Rundung in Verkaufsbelegen</a></span></dt><dt><span class="sect1"><a href="ch02s20.html">2.20. Einstellungen pro Mandant</a></span></dt><dt><span class="sect1"><a href="ch02s21.html">2.21. kivitendo ERP verwenden</a></span></dt></dl></dd><dt><span class="chapter"><a href="ch03.html">3. Features und Funktionen</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch03.html#features.periodic-invoices">3.1. Wiederkehrende Rechnungen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.introduction">3.1.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.configuration">3.1.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.variables">3.1.3. Spezielle Variablen</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.reports">3.1.4. Auflisten</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.task-server">3.1.5. Erzeugung der eigentlichen Rechnungen</a></span></dt><dt><span class="sect2"><a href="ch03.html#features.periodic-invoices.create-for-current-month">3.1.6. Erste Rechnung für aktuellen Monat erstellen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s02.html">3.2. Bankerweiterung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s02.html#features.bank.introduction">3.2.1. Einführung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s03.html">3.3. Dokumentenvorlagen und verfügbare Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.einf%C3%BChrung">3.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.variablen-ausgeben">3.3.2. Variablen ausgeben</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.verwendung-in-druckbefehlen">3.3.3. Verwendung in Druckbefehlen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.tag-style">3.3.4. Anfang und Ende der Tags verändern</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.zuordnung-dateinamen">3.3.5. Zuordnung von den Dateinamen zu den Funktionen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.dateinamen-erweitert">3.3.6. Sprache, Drucker und E-Mail</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.allgemeine-variablen">3.3.7. Allgemeine Variablen, die in allen Vorlagen vorhanden
         sind</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.invoice">3.3.8. Variablen in Rechnungen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.dunning">3.3.9. Variablen in Mahnungen und Rechnungen Ã¼ber Mahngebühren</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.andere-vorlagen">3.3.10. Variablen in anderen Vorlagen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.bloecke">3.3.11. Blöcke, bedingte Anweisungen und Schleifen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.markup">3.3.12. Markup-Code zur Textformatierung innerhalb von
-        Formularen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.anrede">3.3.13. Hinweise zur Anrede</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s04.html">3.4. Excel-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s04.html#excel-templates.summary">3.4.1. Zusammenfassung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.usage">3.4.2. Bedienung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.syntax">3.4.3. Variablensyntax</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.limitations">3.4.4. Einschränkungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s05.html">3.5. Mandantenkonfiguration Lager</a></span></dt><dt><span class="sect1"><a href="ch03s06.html">3.6. Schweizer Kontenpläne</a></span></dt><dt><span class="sect1"><a href="ch03s07.html">3.7. Artikelklassifizierung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s07.html#d0e6515">3.7.1. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6520">3.7.2. Basisklassifizierung</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6550">3.7.3. Attribute</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6581">3.7.4. Zwei-Zeichen Abkürzung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s08.html">3.8. Dateiverwaltung (Mini-DMS)</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s08.html#d0e6593">3.8.1. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6620">3.8.2. Struktur</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6672">3.8.3. Anwendung</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6715">3.8.4. Konfigurierung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s09.html">3.9. Webshop-Api</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s09.html#d0e6780">3.9.1. Rechte für die Webshopapi</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6795">3.9.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6803">3.9.3. Webshopartikel</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6827">3.9.4. Bestellimport</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6880">3.9.5. Mapping der Daten</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s10.html">3.10. ZUGFeRD Rechnungen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.preamble">3.10.1. Vorbedingung</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.summary">3.10.2. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.create_zugferd_bills">3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.read_zugferd_bills">3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="ch04.html">4. Entwicklerdokumentation</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch04.html#devel.globals">4.1. Globale Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04.html#d0e6969">4.1.1. Wie sehen globale Variablen in Perl aus?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7070">4.1.2. Warum sind globale Variablen ein Problem?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7103">4.1.3. Kanonische globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7491">4.1.4. Ehemalige globale Variablen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s02.html">4.2. Entwicklung unter FastCGI</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.general">4.2.1. Allgemeines</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.exiting">4.2.2. Programmende und Ausnahmen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.globals">4.2.3. Globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.performance">4.2.4. Performance und Statistiken</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s03.html">4.3. Programmatische API-Aufrufe</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.introduction">4.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.client_selection">4.3.2. Wahl des Mandanten</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.http_basic_authentication">4.3.3. HTTP-»Basic«-Authentifizierung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.authentication_via_parameters">4.3.4. Authentifizierung mit Parametern</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.examples">4.3.5. Beispiele</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s04.html">4.4. SQL-Upgradedateien</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.introduction">4.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format">4.4.2. Format der Kontrollinformationen</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format-perl-files">4.4.3. Format von in Perl geschriebenen
+        Formularen</a></span></dt><dt><span class="sect2"><a href="ch03s03.html#dokumentenvorlagen-und-variablen.anrede">3.3.13. Hinweise zur Anrede</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s04.html">3.4. Excel-Vorlagen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s04.html#excel-templates.summary">3.4.1. Zusammenfassung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.usage">3.4.2. Bedienung</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.syntax">3.4.3. Variablensyntax</a></span></dt><dt><span class="sect2"><a href="ch03s04.html#excel-templates.limitations">3.4.4. Einschränkungen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s05.html">3.5. Mandantenkonfiguration Lager</a></span></dt><dt><span class="sect1"><a href="ch03s06.html">3.6. Schweizer Kontenpläne</a></span></dt><dt><span class="sect1"><a href="ch03s07.html">3.7. Artikelklassifizierung</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s07.html#d0e6517">3.7.1. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6522">3.7.2. Basisklassifizierung</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6552">3.7.3. Attribute</a></span></dt><dt><span class="sect2"><a href="ch03s07.html#d0e6583">3.7.4. Zwei-Zeichen Abkürzung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s08.html">3.8. Dateiverwaltung (Mini-DMS)</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s08.html#d0e6595">3.8.1. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6622">3.8.2. Struktur</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6674">3.8.3. Anwendung</a></span></dt><dt><span class="sect2"><a href="ch03s08.html#d0e6717">3.8.4. Konfigurierung</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s09.html">3.9. Webshop-Api</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s09.html#d0e6782">3.9.1. Rechte für die Webshopapi</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6797">3.9.2. Konfiguration</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6805">3.9.3. Webshopartikel</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6829">3.9.4. Bestellimport</a></span></dt><dt><span class="sect2"><a href="ch03s09.html#d0e6882">3.9.5. Mapping der Daten</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch03s10.html">3.10. ZUGFeRD Rechnungen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.preamble">3.10.1. Vorbedingung</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.summary">3.10.2. Ãœbersicht</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.create_zugferd_bills">3.10.3. Erstellen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt><dt><span class="sect2"><a href="ch03s10.html#features.zugferd.read_zugferd_bills">3.10.4. Einlesen von ZUGFeRD Rechnungen in Kivitendo</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="ch04.html">4. Entwicklerdokumentation</a></span></dt><dd><dl><dt><span class="sect1"><a href="ch04.html#devel.globals">4.1. Globale Variablen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04.html#d0e6971">4.1.1. Wie sehen globale Variablen in Perl aus?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7072">4.1.2. Warum sind globale Variablen ein Problem?</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7105">4.1.3. Kanonische globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04.html#d0e7493">4.1.4. Ehemalige globale Variablen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s02.html">4.2. Entwicklung unter FastCGI</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.general">4.2.1. Allgemeines</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.exiting">4.2.2. Programmende und Ausnahmen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.globals">4.2.3. Globale Variablen</a></span></dt><dt><span class="sect2"><a href="ch04s02.html#devel.fcgi.performance">4.2.4. Performance und Statistiken</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s03.html">4.3. Programmatische API-Aufrufe</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.introduction">4.3.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.client_selection">4.3.2. Wahl des Mandanten</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.http_basic_authentication">4.3.3. HTTP-»Basic«-Authentifizierung</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.authentication_via_parameters">4.3.4. Authentifizierung mit Parametern</a></span></dt><dt><span class="sect2"><a href="ch04s03.html#dev-programmatic-api-calls.examples">4.3.5. Beispiele</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s04.html">4.4. SQL-Upgradedateien</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.introduction">4.4.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format">4.4.2. Format der Kontrollinformationen</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.format-perl-files">4.4.3. Format von in Perl geschriebenen
         Datenbankupgradescripten</a></span></dt><dt><span class="sect2"><a href="ch04s04.html#db-upgrade-files.dbupgrade-tool">4.4.4. Hilfsscript dbupgrade2_tool.pl</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s05.html">4.5. Translations and languages</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s05.html#translations-languages.introduction">4.5.1. Introduction</a></span></dt><dt><span class="sect2"><a href="ch04s05.html#translations-languages.character-set">4.5.2. Character set</a></span></dt><dt><span class="sect2"><a href="ch04s05.html#translations-languages.file-structure">4.5.3. File structure</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s06.html">4.6. Die kivitendo-Test-Suite</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.intro">4.6.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.prerequisites">4.6.2. Voraussetzungen</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.execution">4.6.3. Existierende Tests ausführen</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.meaning_of_scripts">4.6.4. Bedeutung der verschiedenen Test-Scripte</a></span></dt><dt><span class="sect2"><a href="ch04s06.html#devel.testsuite.create_new">4.6.5. Neue Test-Scripte erstellen</a></span></dt></dl></dd><dt><span class="sect1"><a href="ch04s07.html">4.7. Stil-Richtlinien</a></span></dt><dt><span class="sect1"><a href="ch04s08.html">4.8. Dokumentation erstellen</a></span></dt><dd><dl><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.introduction">4.8.1. Einführung</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.required-software">4.8.2. Benötigte Software</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.build">4.8.3. PDFs und HTML-Seiten erstellen</a></span></dt><dt><span class="sect2"><a href="ch04s08.html#devel.build-doc.repository">4.8.4. Einchecken in das Git-Repository</a></span></dt></dl></dd></dl></dd></dl></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left">&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right">&nbsp;<a accesskey="n" href="ch01.html">Weiter</a></td></tr><tr><td width="40%" align="left" valign="top">&nbsp;</td><td width="20%" align="center">&nbsp;</td><td width="40%" align="right" valign="top">&nbsp;Kapitel 1. Aktuelle Hinweise</td></tr></table></div></body></html>
\ No newline at end of file
index 6421664..d27fed3 100644 (file)
Binary files a/doc/kivitendo-Dokumentation.pdf and b/doc/kivitendo-Dokumentation.pdf differ
diff --git a/image/glass14x14.png b/image/glass14x14.png
new file mode 100644 (file)
index 0000000..e6c9d1b
Binary files /dev/null and b/image/glass14x14.png differ
index 9fc14b6..d231d10 100644 (file)
@@ -113,7 +113,7 @@ ns.eval_json_result = function(data) {
 
       // ## jQuery UI dialog plugin ##
 
-      // Opening and closing and closing a popup
+      // Opening and closing a popup
       else if (action[0] == 'dialog:open')          kivi.popup_dialog(action[1]);
       else if (action[0] == 'dialog:close')         $(action[1]).dialog('close');
 
@@ -159,6 +159,7 @@ ns.eval_json_result = function(data) {
       else if (action[0] == 'run')                  kivi.run(action[1], action.slice(2, action.length));
       else if (action[0] == 'run_once_for')         kivi.run_once_for(action[1], action[2], action[3]);
       else if (action[0] == 'scroll_into_view')     $(action[1])[0].scrollIntoView();
+      else if (action[0] == 'set_cursor_position')  kivi.set_cursor_position(action[1], action[2]);
 
       else                                          console.log('Unknown action: ' + action[0]);
 
index 20ecde0..4a9ba9f 100644 (file)
@@ -20,15 +20,6 @@ namespace('kivi.MassInvoiceCreatePrint', function(ns) {
     return false;
   };
 
-  this.submitMassCreationForm = function() {
-    if (!kivi.MassInvoiceCreatePrint.checkDeliveryOrderSelection())
-      return false;
-
-    $('body').addClass('loading');
-    kivi.submit_form_with_action('form', 'MassInvoiceCreatePrint/create_invoices');
-    return false;
-  };
-
   this.createPrintAllInitialize = function() {
     kivi.popup_dialog({
       id: 'create_print_all_dialog',
index a76ad8a..77dfe5c 100644 (file)
@@ -350,6 +350,19 @@ namespace("kivi", function(ns) {
       editor.on('instanceReady', function() { ns.focus_ckeditor($e); });
   };
 
+  ns.filter_select = function() {
+    var $input  = $(this);
+    var $select = $('#' + $input.data('select-id'));
+    var filter  = $input.val().toLocaleLowerCase();
+
+    $select.find('option').each(function() {
+      if ($(this).text().toLocaleLowerCase().indexOf(filter) != -1)
+        $(this).show();
+      else
+        $(this).hide();
+    });
+  };
+
   ns.reinit_widgets = function() {
     ns.run_once_for('.datepicker', 'datepicker', function(elt) {
       $(elt).datepicker();
@@ -369,6 +382,9 @@ namespace("kivi", function(ns) {
         kivi.ChartPicker($(elt));
       });
 
+    ns.run_once_for('div.filtered_select input', 'filtered_select', function(elt) {
+      $(elt).bind('change keyup', ns.filter_select);
+    });
 
     var func = kivi.get_function_by_name('local_reinit_widgets');
     if (func)
@@ -646,6 +662,15 @@ namespace("kivi", function(ns) {
     $input.parent().replaceWith($area);
     $area.focus();
   };
+
+  ns.set_cursor_position = function(selector, position) {
+    var $input = $(selector);
+    if (position === 'end')
+      position = $input.val().length;
+
+    $input.prop('selectionStart', position);
+    $input.prop('selectionEnd',   position);
+  };
 });
 
 kivi = namespace('kivi');
index 5dc0690..c63bc87 100755 (executable)
@@ -263,6 +263,8 @@ $self->{texts} = {
   'All groups'                  => 'Alle Gruppen',
   'All modules'                 => 'Alle Module',
   'All partsgroups'             => 'Alle Warengruppen',
+  'All payments have already been posted.' => 'Es wurden bereits alle Zahlungen verbucht.',
+  'All payments must be posted before the payment list can be downloaded.' => 'Alle Zahlungen müssen verbucht werden, bevor die Zahlungsliste heruntergeladen werden kann.',
   '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 Dokumenten-Ordnern ausgestattet.',
@@ -270,6 +272,7 @@ $self->{texts} = {
   '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.',
   'All users'                   => 'Alle BenutzerInnen',
+  'Allocations didn\'t pass constraints' => 'Keine Verfügbarkeit wegen Lagereinschränkung',
   'Allow access'                => 'Zugriff erlauben',
   'Allow conversion from sales orders to sales invoices' => 'Umwandlung von Verkaufsaufträgen in Verkaufsrechnungen zulassen',
   'Allow conversion from sales quotations to sales invoices' => 'Umwandlung von Verkaufsangeboten in Verkaufsrechnungen zulassen',
@@ -533,6 +536,7 @@ $self->{texts} = {
   'Cancel Accounts Payables Transaction' => 'Kreditorenbuchung stornieren',
   'Cancel Accounts Receivables Transaction' => 'Debitorenbuchung stornieren',
   'Cancelling is disallowed. Either undo or balance the current payments until the open amount matches the invoice amount' => 'Storno verboten, da Zahlungen zum Beleg vorhanden sind. Entweder die Zahlungen löschen oder mit umgekehrten Vorzeichen ausbuchen, sodass der offene Betrag dem Rechnungsbetrag entspricht.',
+  'Cannot allocate parts.'      => 'Es sind nicht genügend Artikel vorhanden',
   'Cannot change transaction in a closed period!' => 'In einem bereits abgeschlossenen Zeitraum kann keine Buchung verändert werden!',
   'Cannot check correct WebDAV folder' => 'Kann nicht den richtigen WebDAV Pfad Ã¼berprüfen',
   'Cannot delete account!'      => 'Konto kann nicht gelöscht werden!',
@@ -598,6 +602,7 @@ $self->{texts} = {
   'Charge'                      => 'Berechnen',
   'Charge Number'               => 'Chargennummer',
   'Charge number'               => 'Chargennummer',
+  'Chargenumbers'               => 'Chargennummern',
   'Charset'                     => 'Zeichensatz',
   'Chart'                       => 'Buchungskonto',
   'Chart Type'                  => 'Kontentyp',
@@ -3378,6 +3383,7 @@ $self->{texts} = {
   'The first reason is that kivitendo contained a bug which resulted in the wrong taxkeys being recorded for transactions in which two entries are posted for the same chart with different taxkeys.' => 'Der erste Grund war ein Fehler in kivitendo, der dazu führte, dass bei einer Transaktion, bei der zwei Buchungen mit unterschiedlichen Steuerschlüsseln auf dasselbe Konto durchgeführt wurden, die falschen Steuerschlüssel gespeichert wurden.',
   'The follow-up date is missing.' => 'Das Wiedervorlagedatum fehlt.',
   'The following currencies have been used, but they are not defined:' => 'Die folgenden Währungen wurden benutzt, sind aber nicht ordnungsgemäß in der Datenbank eingetragen:',
+  'The following delivery orders could not be processed because they are already closed: #1' => 'Die folgenden Lieferscheine konnten nicht verarbeitet werden, da sie bereits geschlossen sind: #1',
   'The following drafts have been saved and can be loaded.' => 'Die folgenden Entwürfe wurden gespeichert und können geladen werden.',
   'The following groups are valid for this client' => 'Die folgenden Gruppen sind für diesen Mandanten gültig',
   'The following is only a preview.' => 'Das Folgende ist nur eine Vorschau.',
@@ -4121,6 +4127,7 @@ $self->{texts} = {
   'brutto'                      => 'brutto',
   'building data'               => 'Verarbeite Daten',
   'building report'             => 'Erstelle Bericht',
+  'can not allocate #1 units of #2, missing #3 units' => 'Kann keine #1 Einheiten von #2 belegen, es fehlen #3 Einheiten',
   'can only parse a pdf file'   => 'Kann nur eine gültige PDF-Datei verwenden.',
   'cash'                        => 'Ist-Versteuerung',
   'chargenumber #1'             => 'Chargennummer #1',
@@ -4269,6 +4276,7 @@ $self->{texts} = {
   'our vendor number at customer' => 'Unsere Lieferanten-Nr. beim Kunden',
   'parsing csv'                 => 'Parse CSV Daten',
   'part'                        => 'Ware',
+  'part \'#\'1 in bin \'#2\' only with qty #3 (need additional #4) and chargenumber \'#5\'.' => 'Artikel \'#1\' im \'#2\' nur mit der Menge #3 (noch #4 benötig) und Chargennummer \'#5\'.',
   'part_list'                   => 'Warenliste',
   'percental'                   => 'prozentual',
   'periodic'                    => 'Aufwandsmethode',
index a8df39e..98641e2 100644 (file)
@@ -278,6 +278,7 @@ $self->{texts} = {
   'Allow the following users access to my follow-ups:' => '',
   'Allow to delete generated printfiles' => '',
   'Already counted'             => '',
+  'Already imported entries (duplicates)' => '',
   'Always edit assembly items (user can change/delete items even if assemblies are already produced)' => '',
   'Always save orders with a projectnumber (create new projects)' => '',
   'Amended Advance Turnover Tax Return' => '',
@@ -1134,6 +1135,7 @@ $self->{texts} = {
   'Dunnings'                    => '',
   'Dunnings (Id -- Dunning Date --Dunning Level -- Dunning Fee)' => '',
   'Dunningstatistic'            => '',
+  'Duplicate'                   => '',
   'Duplicate in CSV file'       => '',
   'Duplicate in database'       => '',
   'During the next update a taxkey 0 with tax rate of 0 will automatically created.' => '',
@@ -1275,6 +1277,8 @@ $self->{texts} = {
   'Enter the requested execution date or leave empty for the quickest possible execution:' => '',
   'Entries for which automatic conversion failed:' => '',
   'Entries for which automatic conversion succeeded:' => '',
+  'Entries ready to import'     => '',
+  'Entries with errors'         => '',
   'Equity'                      => '',
   'Erfolgsrechnung'             => '',
   'Error'                       => '',
@@ -1314,7 +1318,8 @@ $self->{texts} = {
   'Error: Invalid language'     => '',
   'Error: Invalid part'         => '',
   'Error: Invalid part type'    => '',
-  'Error: Invalid parts group'  => '',
+  'Error: Invalid parts group id #1' => '',
+  'Error: Invalid parts group name #1' => '',
   'Error: Invalid payment terms' => '',
   'Error: Invalid price factor' => '',
   'Error: Invalid price group'  => '',
@@ -1666,6 +1671,8 @@ $self->{texts} = {
   'Import result'               => '',
   'Import scanned documents'    => '',
   'Importdate'                  => '',
+  'Imported'                    => '',
+  'Imported entries'            => '',
   'In addition to the above date functions, subtract the following amount of days from the calculated date as a buffer.' => '',
   'In order to do that hit the button "Delete transaction".' => '',
   'In order to migrate the old folder structure into the new structure you have to chose which client the old structure will be assigned to.' => '',
@@ -1873,6 +1880,7 @@ $self->{texts} = {
   'Loading...'                  => '',
   'Local Bank Code'             => '',
   'Local Tax Office Preferences' => '',
+  'Local account'               => '',
   'Local account number'        => '',
   'Local bank account'          => '',
   'Local bank code'             => '',
@@ -1901,6 +1909,8 @@ $self->{texts} = {
   'MD'                          => '',
   'MIME type'                   => '',
   'MT940 import'                => '',
+  'MT940 import preview'        => '',
+  'MT940 import result'         => '',
   'Mails'                       => '',
   'Main Contact Person'         => '',
   'Main Preferences'            => '',
@@ -2042,6 +2052,7 @@ $self->{texts} = {
   'No assembly has been selected yet.' => '',
   'No background job has been created yet.' => '',
   'No bank account chosen!'     => '',
+  'No bank account configured for bank code/BIC #1, account number/IBAN #2.' => '',
   'No bank account flagged for ZUGFeRD usage was found.' => '',
   'No bank information has been entered in this customer\'s master data entry. You cannot create bank collections unless you enter bank information.' => '',
   'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => '',
@@ -2064,6 +2075,7 @@ $self->{texts} = {
   'No email for user with login #1 defined.' => '',
   'No email recipient for customer #1 defined.' => '',
   'No end date given, setting to today' => '',
+  'No entries can be imported.' => '',
   'No entries have been imported yet.' => '',
   'No entries have been selected.' => '',
   'No errors have occurred.'    => '',
@@ -2234,6 +2246,7 @@ $self->{texts} = {
   'Orphaned'                    => '',
   'Orphaned currencies'         => '',
   'Other Matches'               => '',
+  'Other party'                 => '',
   'Other recipients'            => '',
   'Other users\' follow-ups'    => '',
   'Other values are ignored.'   => '',
@@ -2362,7 +2375,6 @@ $self->{texts} = {
   'Please contact your administrator or a service provider.' => '',
   'Please contact your administrator.' => '',
   'Please correct the settings and try again or deactivate that client.' => '',
-  'Please create a CSV import profile called "MT940" for the import type bank transactions:' => '',
   'Please define a taxkey for the following taxes and run the update again:' => '',
   'Please do so in the administration area.' => '',
   'Please enter a profile name.' => '',
@@ -2643,6 +2655,7 @@ $self->{texts} = {
   'Remittance information prefix' => '',
   'Remote Bank Code'            => '',
   'Remote Name/Customer/Description' => '',
+  'Remote account'              => '',
   'Remote account number'       => '',
   'Remote bank code'            => '',
   'Remote name'                 => '',
@@ -3224,7 +3237,6 @@ $self->{texts} = {
   'The IBAN is missing.'        => '',
   'The ID #1 is not a valid database ID.' => '',
   'The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.' => '',
-  'The MT940 import needs an import profile called MT940' => '',
   'The Mail strings have been saved.' => '',
   'The PDF has been created'    => '',
   'The PDF has been printed'    => '',
@@ -3364,6 +3376,7 @@ $self->{texts} = {
   'The first reason is that kivitendo contained a bug which resulted in the wrong taxkeys being recorded for transactions in which two entries are posted for the same chart with different taxkeys.' => '',
   'The follow-up date is missing.' => '',
   'The following currencies have been used, but they are not defined:' => '',
+  'The following delivery orders could not be processed because they are already closed: #1' => '',
   'The following drafts have been saved and can be loaded.' => '',
   'The following groups are valid for this client' => '',
   'The following is only a preview.' => '',
@@ -3686,6 +3699,7 @@ $self->{texts} = {
   'To (time)'                   => '',
   'To Date'                     => '',
   'To continue please change the taxkey 0 to another value.' => '',
+  'To import'                   => '',
   'To upload images: Please create shoppart first' => '',
   'To user login'               => '',
   'Toggle marker'               => '',
@@ -3698,6 +3712,7 @@ $self->{texts} = {
   'Total'                       => '',
   'Total Fees'                  => '',
   'Total Sales Orders Value'    => '',
+  'Total number of entries'     => '',
   'Total stock value'           => '',
   'Total sum'                   => '',
   'Total weight'                => '',
@@ -3711,6 +3726,7 @@ $self->{texts} = {
   'Transaction ID missing.'     => '',
   'Transaction Value'           => '',
   'Transaction Value Currency Code' => '',
+  'Transaction date'            => '',
   'Transaction deleted!'        => '',
   'Transaction description'     => '',
   'Transaction has already been cancelled!' => '',
@@ -4040,6 +4056,7 @@ $self->{texts} = {
   'You have to grant users access to one or more clients.' => '',
   'You have to specify a department.' => '',
   'You have to specify an execution date for each antry.' => '',
+  'You have to upload an MT940 file to import.' => '',
   'You must chose a user.'      => '',
   'You must enter a name for your new print templates.' => '',
   'You must not change this AP transaction.' => '',
@@ -4061,6 +4078,7 @@ $self->{texts} = {
   'ZUGFeRD import'              => '',
   'ZUGFeRD invoice'             => '',
   'ZUGFeRD notes for each invoice' => '',
+  'ZUGFeRD settings'            => '',
   'Zeitraum'                    => '',
   'Zero amount posting!'        => '',
   'Zip'                         => '',
index efc2d54..0b023b6 100644 (file)
@@ -7,17 +7,18 @@ __PACKAGE__->meta->setup(
   columns => [
     dummy => { type => 'numeric', precision => 2, scale => 12 },
     inty  => { type => 'integer' },
+    miny  => { type => 'integer' },
   ]
 );
 
 use SL::DB::Helper::AttrDuration;
 
 __PACKAGE__->attr_duration('dummy');
-__PACKAGE__->attr_duration_minutes('inty');
+__PACKAGE__->attr_duration_minutes('inty', 'miny');
 
 package main;
 
-use Test::More tests => 120;
+use Test::More tests => 130;
 use Test::Exception;
 
 use strict;
@@ -218,6 +219,22 @@ is($item->inty_as_minutes,         1,      'write as_duration_string 03:1 read a
 is($item->inty_as_hours,           3,      'write as_duration_string 03:1 read as_hours');
 is($item->inty_as_duration_string, "3:01", 'write as_duration_string 03:1 read as_duration_string');
 
+local %::myconfig = (numberformat => "1.000,00");
+
+$item = new_item(miny_in_hours => 2.5);
+is($item->miny,                    150,    'write in_hours 2.5 read raw');
+is($item->miny_as_minutes,         30,     'write in_hours 2.5 read as_minutes');
+is($item->miny_as_hours,           2,      'write in_hours 2.5 read as_hours');
+is($item->miny_in_hours,           2.5,    'write in_hours 2.5 read in_hours');
+is($item->miny_in_hours_as_number, '2,50', 'write in_hours 2.5 read in_hours_as_number');
+
+$item = new_item(miny_in_hours_as_number => '4,25');
+is($item->miny,                    255,    'write in_hours_as_number 4,25 read raw');
+is($item->miny_as_minutes,         15,     'write in_hours_as_number 4,25 read as_minutes');
+is($item->miny_as_hours,           4,      'write in_hours_as_number 4,25 read as_hours');
+is($item->miny_in_hours,           4.25,   'write in_hours_as_number 4,25 read in_hours');
+is($item->miny_in_hours_as_number, '4,25', 'write in_hours_as_number 4,25 read in_hours_as_number');
+
 # Parametervalidierung
 throws_ok { new_item()->inty_as_duration_string('invalid') } qr/invalid.*format/i, 'invalid duration format';
 
diff --git a/t/wh/inventory.t b/t/wh/inventory.t
new file mode 100644 (file)
index 0000000..a10b9cf
--- /dev/null
@@ -0,0 +1,285 @@
+use strict;
+use Test::More;
+use Test::Exception;
+
+use lib 't';
+
+use SL::Dev::Part qw(new_part new_assembly);
+use SL::Dev::Inventory qw(create_warehouse_and_bins set_stock);
+use SL::Dev::Record qw(create_sales_order);
+
+use_ok 'Support::TestSetup';
+use_ok 'SL::DB::Bin';
+use_ok 'SL::DB::Part';
+use_ok 'SL::DB::Warehouse';
+use_ok 'SL::DB::Inventory';
+use_ok 'SL::WH';
+use_ok 'SL::Helper::Inventory';
+
+Support::TestSetup::login();
+
+my ($wh, $bin1, $bin2, $assembly1, $part1, $part2);
+
+reset_db();
+create_standard_stock();
+
+
+# simple stock in, get_stock, get_onhand
+set_stock(
+  part => $part1,
+  qty => 25,
+  bin => $bin1,
+);
+
+is(SL::Helper::Inventory::get_stock(part => $part1), "25.00000", 'simple get_stock works');
+is(SL::Helper::Inventory::get_onhand(part => $part1), "25.00000", 'simple get_onhand works');
+
+# stock on some more, get_stock, get_onhand
+
+WH->transfer({
+  parts_id          => $part1->id,
+  qty               => 15,
+  transfer_type     => 'stock',
+  dst_warehouse_id  => $bin1->warehouse_id,
+  dst_bin_id        => $bin1->id,
+  comment           => 'more',
+});
+
+WH->transfer({
+  parts_id          => $part1->id,
+  qty               => 20,
+  transfer_type     => 'stock',
+  chargenumber      => '298345',
+  dst_warehouse_id  => $bin1->warehouse_id,
+  dst_bin_id        => $bin1->id,
+  comment           => 'more',
+});
+
+is(SL::Helper::Inventory::get_stock(part => $part1), "60.00000", 'normal get_stock works');
+is(SL::Helper::Inventory::get_onhand(part => $part1), "60.00000", 'normal get_onhand works');
+
+# allocate some stuff
+
+my @allocations = SL::Helper::Inventory::allocate(
+  part => $part1,
+  qty  => 12,
+);
+
+is_deeply(\%{ $allocations[0] }, {
+   bestbefore        => undef,
+   bin_id            => $bin1->id,
+   chargenumber      => '',
+   parts_id          => $part1->id,
+   qty               => 12,
+   warehouse_id      => $wh->id,
+   comment           => undef, # comment is not a partition so is not set by allocate
+   for_object_id     => undef,
+ }, 'allocation works');
+
+# allocate something where more than one result will match
+
+@allocations = SL::Helper::Inventory::allocate(
+  part => $part1,
+  qty  => 55,
+);
+
+is_deeply(\@allocations, [
+  {
+    bestbefore        => undef,
+    bin_id            => $bin1->id,
+    chargenumber      => '',
+    parts_id          => $part1->id,
+    qty               => '40.00000',
+    warehouse_id      => $wh->id,
+    comment           => undef,
+    for_object_id     => undef,
+  },
+  {
+    bestbefore        => undef,
+    bin_id            => $bin1->id,
+    chargenumber      => '298345',
+    parts_id          => $part1->id,
+    qty               => '15',
+    warehouse_id      => $wh->id,
+    comment           => undef,
+    for_object_id     => undef,
+  }
+], 'complex allocation works');
+
+# try to allocate too much
+
+dies_ok(sub {
+  SL::Helper::Inventory::allocate(part => $part1, qty => 100)
+},
+"allocate too much dies");
+
+# produce something
+
+reset_db();
+create_standard_stock();
+
+set_stock(
+  part => $part1,
+  qty => 5,
+  bin => $bin1,
+);
+set_stock(
+  part => $part2,
+  qty => 10,
+  bin => $bin1,
+);
+
+
+my @alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 3);
+my @alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 3);
+
+SL::Helper::Inventory::produce_assembly(
+  part          => $assembly1,
+  qty           => 3,
+  allocations => [ @alloc1, @alloc2 ],
+
+  # where to put it
+  bin          => $bin1,
+  chargenumber => "537",
+);
+
+is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce works');
+is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
+is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
+
+# produce the same using auto_allocation
+
+reset_db();
+create_standard_stock();
+
+set_stock(
+  part => $part1,
+  qty => 5,
+  bin => $bin1,
+);
+set_stock(
+  part => $part2,
+  qty => 10,
+  bin => $bin1,
+);
+
+SL::Helper::Inventory::produce_assembly(
+  part          => $assembly1,
+  qty           => 3,
+  auto_allocate => 1,
+
+  # where to put it
+  bin          => $bin1,
+  chargenumber => "537",
+);
+
+is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with auto allocation works');
+is(SL::Helper::Inventory::get_stock(part => $part1), "2.00000", 'and consumes...');
+is(SL::Helper::Inventory::get_stock(part => $part2), "7.00000", '..the materials');
+
+# try to produce without allocations dies
+
+dies_ok(sub {
+SL::Helper::Inventory::produce_assembly(
+  part          => $assembly1,
+  qty           => 3,
+
+  # where to put it
+  bin          => $bin1,
+  chargenumber => "537",
+);
+}, "producing without allocations dies");
+
+# try to produce with insufficient allocations dies
+
+@alloc1 = SL::Helper::Inventory::allocate(part => $part1, qty => 1);
+@alloc2 = SL::Helper::Inventory::allocate(part => $part2, qty => 1);
+
+dies_ok(sub {
+SL::Helper::Inventory::produce_assembly(
+  part          => $assembly1,
+  qty           => 3,
+  allocations => [ @alloc1, @alloc2 ],
+
+  # where to put it
+  bin          => $bin1,
+  chargenumber => "537",
+);
+}, "producing with insufficient allocations dies");
+
+
+
+# bestbefore tests
+
+reset_db();
+create_standard_stock();
+
+set_stock(
+  part => $part1,
+  qty => 5,
+  bin => $bin1,
+);
+set_stock(
+  part => $part2,
+  qty => 10,
+  bin => $bin1,
+);
+
+
+
+SL::Helper::Inventory::produce_assembly(
+  part          => $assembly1,
+  qty           => 3,
+  auto_allocate => 1,
+
+  bin               => $bin1,
+  chargenumber      => "537",
+  bestbefore        => DateTime->today->clone->add(days => -14), # expired 2 weeks ago
+  shippingdate      => DateTime->today->clone->add(days => 1),
+);
+
+is(SL::Helper::Inventory::get_stock(part => $assembly1), "3.00000", 'produce with bestbefore works');
+is(SL::Helper::Inventory::get_onhand(part => $assembly1), "3.00000", 'produce with bestbefore works');
+is(SL::Helper::Inventory::get_stock(
+  part       => $assembly1,
+  bestbefore => DateTime->today,
+), undef, 'get_stock with bestbefore date skips expired');
+{
+  local $::instance_conf->data->{show_bestbefore} = 1;
+  is(SL::Helper::Inventory::get_onhand(
+    part       => $assembly1,
+  ), undef, 'get_onhand with bestbefore skips expired as of today');
+}
+
+{
+  local $::instance_conf->data->{show_bestbefore} = 0;
+  is(SL::Helper::Inventory::get_onhand(
+    part       => $assembly1,
+  ), "3.00000", 'get_onhand without bestbefore finds all');
+}
+
+
+sub reset_db {
+  SL::DB::Manager::Order->delete_all(all => 1);
+  SL::DB::Manager::Inventory->delete_all(all => 1);
+  SL::DB::Manager::Assembly->delete_all(all => 1);
+  SL::DB::Manager::Part->delete_all(all => 1);
+  SL::DB::Manager::Bin->delete_all(all => 1);
+  SL::DB::Manager::Warehouse->delete_all(all => 1);
+}
+
+sub create_standard_stock {
+  ($wh, $bin1) = create_warehouse_and_bins();
+  $bin2 = SL::DB::Bin->new(description => "Bin 2", warehouse => $wh)->save;
+  $wh->load;
+
+  $assembly1  =  new_assembly(number_of_parts => 2)->save;
+  ($part1, $part2) = map { $_->part } $assembly1->assemblies;
+}
+
+
+reset_db();
+
+done_testing();
+
+1;
index abbc1a1..90ccaff 100644 (file)
@@ -162,7 +162,7 @@ contents={\usebox\shippingAddressBox}
 
 \prg_new_conditional:Nnn \kivi_if_Price_col:n {T} {
        \prop_get:cnN {l_kivi_col_#1_prop} {colspec} \l_tmpa_tl
-       \tl_if_eq:NnTF \l_tmpa_tl {Price}
+       \exp_args:NV \tl_if_eq:nnTF \l_tmpa_tl {Price}
                {\prg_return_true:}
                {\prg_return_false:}
 }
@@ -191,7 +191,7 @@ contents={\usebox\shippingAddressBox}
                                                \dim_use:c {l_kivi_tab_##1_dim}+2\g_kivi_tabcolsep_dim
                                        }
                                }
-                               \tl_gput_right:Nn \g_kivi_Pricing_colspec_tl {>{\raggedleft\arraybackslash}p{\dim_use:c {l_kivi_tab_##1_dim}}}
+                               \tl_gput_right:Nn \g_kivi_Pricing_colspec_tl {K{\dim_use:c {l_kivi_tab_##1_dim}}}
                                \kivi_if_Price_col:nT {##1} {\tl_gput_right:Nn \g_kivi_Pricing_colspec_tl {<{\__kivi_tab_column_currency:}}}
                        }
                }
@@ -200,7 +200,8 @@ contents={\usebox\shippingAddressBox}
        \tl_gput_right:Nn \g_kivi_Pricing_colspec_tl {@{}}
 }
 
-\newcolumntype{P}[1]{>{\raggedleft\arraybackslash}p{#1}<{\__kivi_tab_column_currency:}}
+\newcolumntype{K}[1]{>{\raggedleft\arraybackslash}p{#1}}
+\newcolumntype{P}[1]{K{#1}<{\__kivi_tab_column_currency:}}
 
 \RequirePackage{tcolorbox}
 \tcbuselibrary{breakable, skins}
index 2000b8c..d7d81a9 100644 (file)
            <td nowrap>[% 'Customer Number' | $T8 %]</td>
            <td align=right><input name="l_department" id="l_department" class=checkbox type=checkbox value=Y></td>
            <td nowrap>[% 'Department' | $T8 %]</td>
-
+          </tr>
+          <tr>
+           <td align=right><input name="l_donumber" id="l_donumber" class=checkbox type=checkbox value=Y></td>
+           <td nowrap>[% 'Delivery Order Number' | $T8 %]</td>
+           <td align=right><input name="l_deliverydate" id="l_deliverydate" class=checkbox type=checkbox value=Y></td>
+           <td nowrap>[% 'Delivery Date' | $T8 %]</td>
           </tr>
           <tr>
            <td align=right><input name="l_netamount" id="l_netamount" class=checkbox type=checkbox value="Y" checked></td>
index aecbf1d..e867f7a 100644 (file)
     </td>
    </tr>
 
+   <tr>
+    <th align="right">[%- LxERP.t8('Date Format') %]:</th>
+    <td colspan="10">
+     [% L.select_tag('settings.dateformat', ['dd.mm.yyyy', 'yyyy-mm-dd', 'dd/mm/yyyy', 'mm/dd/yyyy' ], default = SELF.profile.get('dateformat'), style = 'width: 300px') %]
+    </td>
+   </tr>
+
    <tr>
     <th align="right">[%- LxERP.t8('Charset') %]:</th>
     <td colspan="10">[% L.select_tag('settings.charset', SELF.all_charsets, default = SELF.profile.get('charset'), style = 'width: 300px') %]</td>
index 797821a..ba9036c 100644 (file)
@@ -6,7 +6,15 @@
 
 [%- INCLUDE 'common/flash.html' %]
 
-<script type="text/javascript" src="js/show_form_details.js"></script>
+<script type="text/javascript">
+  <!--
+  function copy_debit_to_credit() {
+    var txt = document.getElementsByName('debit_1')[0].value;
+    document.getElementsByName('credit_2')[0].value = txt;
+  };
+  //-->
+  </script>
+  <script type="text/javascript" src="js/show_form_details.js"></script>
 <script type="text/javascript" src="js/follow_up.js"></script>
 <script type="text/javascript" src="js/kivi.Draft.js"></script>
 
index 4fcfa3b..fac160a 100644 (file)
         </tr>
         <tr>
           <th align="right" nowrap>[% 'Project Number' | $T8 %]</th>
-          <td>[%- L.select_tag('globalproject_id', ALL_PROJECTS, title_key = 'projectnumber', default = globalproject_id, with_empty = '1', onChange = "document.getElementById('update_button').click();") %]</td>
+          <td>[% P.project.picker('globalproject_id', globalproject_id, onchange="document.getElementById('update_button').click();") %]</td>
         </tr>
       </table>
     </td>
index 8c9f709..b2f2528 100644 (file)
@@ -28,9 +28,7 @@
 
  <tr>
   <th align="right">[% 'Customer' | $T8 %]</th>
-<td>[% P.customer_vendor.picker('project.customer_id', SELF.project.customer_id, type='customer', fat_set_item=1, style='width: 300px', default=SELF.project.customer_id)%] [%- IF SELF.project.customer_id %]
-[%- END %]
-</td>
+  <td>[% P.customer_vendor.picker('project.customer_id', SELF.project.customer_id, type='customer', fat_set_item=1, style='width: 300px', default=SELF.project.customer_id)%]</td>
  </tr>
 
  <tr>