Feature: Auslagern über Rechnung inkl. Seriennummer == Chargennummer
authorJan Büren <jan@kivitendo.de>
Thu, 27 Aug 2020 15:18:00 +0000 (17:18 +0200)
committerJan Büren <jan@kivitendo.de>
Thu, 27 Aug 2020 15:18:00 +0000 (17:18 +0200)
Prüft, ob die kommaseparierte Liste in serialnumber mit
der Stückzahl in der Position übereinstimmt
Prüft, ob die Seriennummer als Chargennummer vorhanden sind.
Bei Erfolg werden diese Chargen dann ausgelagert.

SL/IS.pm
SL/WH.pm
locale/de/all

index 4e9809b..c743f3b 100644 (file)
--- a/SL/IS.pm
+++ b/SL/IS.pm
@@ -1461,26 +1461,54 @@ sub transfer_out {
 
   foreach my $i (1 .. $form->{rowcount}) {
     next if !$form->{"id_$i"};
-    my ($err, $wh_id, $bin_id) = _determine_wh_and_bin($dbh, $::instance_conf,
-                                                       $form->{"id_$i"},
-                                                       $form->{"qty_$i"},
-                                                       $form->{"unit_$i"});
-    if (!@{ $err } && $wh_id && $bin_id) {
-      push @transfers, {
-        'parts_id'         => $form->{"id_$i"},
-        'qty'              => $form->{"qty_$i"},
-        'unit'             => $form->{"unit_$i"},
-        'transfer_type'    => 'shipped',
-        'src_warehouse_id' => $wh_id,
-        'src_bin_id'       => $bin_id,
-        'project_id'       => $form->{"project_id_$i"},
-        'invoice_id'       => $form->{"invoice_id_$i"},
-        'comment'          => $::locale->text("Default transfer invoice"),
-      };
-    }
 
+    my ($err, $wh_id, $bin_id, $chargenumber);
+
+    if ($::instance_conf->get_sales_serial_eq_charge) {
+      next unless $form->{"serialnumber_$i"};
+      my @serials = split(" ", $form->{"serialnumber_$i"});
+      if (scalar @serials != $form->{"qty_$i"}) {
+        push @errors, $::locale->text("Cannot transfer #1 qty with #2 serial number(s)", $form->{"qty_$i"}, scalar @serials);
+        last;
+      }
+      foreach my $serial (@serials) {
+        ($wh_id, $bin_id, $chargenumber) = WH->get_wh_and_bin_for_charge(chargenumber => $serial);
+
+        push @transfers, {
+            'parts_id'         => $form->{"id_$i"},
+            'qty'              => 1,
+            'unit'             => $form->{"unit_$i"},
+            'transfer_type'    => 'shipped',
+            'src_warehouse_id' => $wh_id,
+            'src_bin_id'       => $bin_id,
+            'chargenumber'     => $chargenumber,
+            'project_id'       => $form->{"project_id_$i"},
+            'invoice_id'       => $form->{"invoice_id_$i"},
+            'comment'          => $::locale->text("Default transfer invoice with charge number"),
+        };
+      }
+      $err = []; # error handling uses @errors direct
+    } else {
+      ($err, $wh_id, $bin_id)    = _determine_wh_and_bin($dbh, $::instance_conf,
+                                                         $form->{"id_$i"},
+                                                         $form->{"qty_$i"},
+                                                         $form->{"unit_$i"});
+      if (!@{ $err } && $wh_id && $bin_id) {
+        push @transfers, {
+          'parts_id'         => $form->{"id_$i"},
+          'qty'              => $form->{"qty_$i"},
+          'unit'             => $form->{"unit_$i"},
+          'transfer_type'    => 'shipped',
+          'src_warehouse_id' => $wh_id,
+          'src_bin_id'       => $bin_id,
+          'project_id'       => $form->{"project_id_$i"},
+          'invoice_id'       => $form->{"invoice_id_$i"},
+          'comment'          => $::locale->text("Default transfer invoice"),
+        };
+      }
+    }
     push @errors, @{ $err };
-  }
+  } # end form rowcount
 
   if (!@errors) {
     WH->transfer(@transfers);
index f05ba4f..979d4de 100644 (file)
--- a/SL/WH.pm
+++ b/SL/WH.pm
 
 package WH;
 
+use Carp qw(croak);
+
 use SL::AM;
 use SL::DBUtils;
+use SL::DB::Inventory;
 use SL::Form;
+use SL::Locale::String qw(t8);
 use SL::Util qw(trim);
 
 use warnings;
@@ -56,7 +60,6 @@ sub transfer {
   require SL::DB::TransferType;
   require SL::DB::Part;
   require SL::DB::Employee;
-  require SL::DB::Inventory;
 
   my $employee   = SL::DB::Manager::Employee->find_by(login => $::myconfig{login});
   my ($now)      = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT current_date|);
@@ -1127,6 +1130,21 @@ $main::lxdebug->enter_sub();
   return ($max_qty_parts, $error);
 }
 
+sub get_wh_and_bin_for_charge {
+  $main::lxdebug->enter_sub();
+
+  my $self     = shift;
+  my %params   = @_;
+
+  croak t8('Need charge number!') unless $params{chargenumber};
+
+  my $inv_item= SL::DB::Manager::Inventory->get_first(where => [chargenumber => $params{chargenumber} ]);
+
+  croak t8("Invalid charge number: #1", $params{chargenumber}) unless (ref $inv_item eq 'SL::DB::Inventory');
+
+  $main::lxdebug->leave_sub();
+  return ($inv_item->warehouse_id, $inv_item->bin_id, $inv_item->chargenumber);
+}
 1;
 
 __END__
@@ -1280,6 +1298,15 @@ The typical params would be:
     'comment'          => $form->{comment}
   );
 
+
+=head2 get_wh_and_bin_for_charge C<$params{chargenumber}>
+
+Gets the first inventory entry with the mandatory chargenumber: C<$params{chargenumber}>.
+Croaks if the chargenumber is missing or no entry currently exists.
+From the found inventory entry the following values and in this order are returned:
+C<warehouse_id>, C<bin_id>, C<chargenumber>.
+
+
 =head3 Prerequisites
 
 All of these prerequisites have to be trueish, otherwise the function will exit
index ded9329..04b0e24 100755 (executable)
@@ -573,6 +573,7 @@ $self->{texts} = {
   'Cannot stock without amount' => 'Kann nicht ohne Menge einlagern!',
   'Cannot storno invoice for a closed period!' => 'Das Rechnungsdatum der zu stornierenden Rechnung fällt in einen abgeschlossenen Zeitraum!',
   'Cannot storno storno invoice!' => 'Kann eine Stornorechnung nicht stornieren',
+  'Cannot transfer #1 qty with #2 serial number(s)' => 'Kann nicht die Menge von #1 mit #2 Seriennummer auslagern.',
   'Cannot transfer negative entries.' => 'Kann keine negativen Mengen auslagern.',
   'Cannot transfer negative quantities.' => 'Negative Mengen können nicht ausgelagert werden.',
   'Cannot transfer. <br> Reason:<br>#1' => 'Kann nicht ein-/auslagern. <br>Grund:<br>#1',
@@ -957,6 +958,7 @@ $self->{texts} = {
   'Default template format'     => 'Standardvorlagenformat',
   'Default transfer delivery order' => 'Standard-Auslagern über Lieferschein',
   'Default transfer invoice'    => 'Standard-Auslagern über Rechnung',
+  'Default transfer invoice with charge number' => 'Standard-Auslagern über Rechnung mit Chargennummer',
   'Default transport article number' => 'Standard Versand / Transport-Erinnerungs-Artikel',
   'Default unit'                => 'Standardeinheit',
   'Default value'               => 'Standardwert',
@@ -1620,6 +1622,7 @@ $self->{texts} = {
   'If item not found, allow creation of new item' => 'Falls Artikel nicht gefunden, erlaube Erfassen eines Neuen',
   'If left empty the default sender from the kivitendo configuration will be used (key \'email_from\' in section \'periodic_invoices\'; current value: #1).' => 'Falls leer, so wird der Standardabsender aus der kivitendo-Konfiguration genutzt (Schlüssel »email_from« in Abschnitt »periodic_invoices«; aktueller Wert: #1).',
   'If missing then the start date will be used.' => 'Falls es fehlt, so wird die erste Rechnung für das Startdatum erzeugt.',
+  'If one or more space separated serial numbers are assigned in a sales invoice, match the charge number of the inventory item. Assumes that Serial Number and Charge Number have 1:1 relation. Otherwise throw a error message for the default sales invoice transfer.' => 'Falls eine oder mehrere Leerzeichen separierte Seriennummern in Verkaufsrechnungen definiert sind, nutz diese als Chargennummern fürs Standard-Auslagern über Rechnung. Seriennummern und eingelagerte Chargen kommen jeweils exakt nur einmal vor. Falls die Chargennummer oder das Mengenverhältnis (1:1) in keinem Lagerort existiert wird eine Fehlermeldung beim Auslagern generiert.',
   'If searching a part from a document and no part is found then offer to create a new part.' => 'Wenn bei der Artikelsuche aus einem Dokument heraus kein Artikel gefunden wird, dann wird ermöglicht, von dort aus einen neuen Artikel anzulegen.',
   'If the article type is set to \'mixed\' then a column called \'part_type\' or called \'pclass\' must be present.' => 'Falls der Artikeltyp auf \'mixed\' gesetzt ist muss entweder eine Spalte \'part_type\' oder \'pclass\' im Import vorhanden sein',
   'If the automatic creation of invoices for fees and interest is switched on for a dunning level then the following accounts will be used for the invoice.' => 'Wenn das automatische Erstellen einer Rechnung über Mahngebühren und Zinsen für ein Mahnlevel aktiviert ist, so werden die folgenden Konten für die Rechnung benutzt.',
@@ -1707,6 +1710,7 @@ $self->{texts} = {
   'Introduction of clients'     => 'Einführung von Mandanten',
   'Inv. Duedate'                => 'Rg. Fälligkeit',
   'Invalid'                     => 'Ungültig',
+  'Invalid charge number: #1'   => 'Ungültige Chargennummer: #1',
   'Invalid combination of ledger account number length. Mismatch length of #1 with length of #2. Please check your account settings. ' => 'Ungültige Kombination der Nummernkreislänge der Sachkonten. Kann nicht eine Länge von #1 und eine Länge von #2 verarbeiten. Bitte entsprechend die Konteneinstellungen überprüfen.',
   'Invalid duration format'     => 'Falsches Format für Zeitdauer',
   'Invalid follow-up ID.'       => 'Ungültige Wiedervorlage-ID.',
@@ -1925,6 +1929,7 @@ $self->{texts} = {
   'Mass Create Print Sales Invoice from Delivery Order' => 'Massenerstellen und Ausdruck von Rechnungen aus Lieferscheinen',
   'Master Data'                 => 'Stammdaten',
   'Master Data Bin Text Deleted' => 'Gelöschte Stammdaten Freitext-Lagerplätze',
+  'Match Sales Invoice Serial numbers with inventory charge numbers?' => 'Gleiche die Seriennummern aus der VK-Rechnung mit den eingelagerten Chargennummern ab?',
   'Matching Price Rules can apply in one of three types:' => 'Preisregeln können Preise in drei Varianten vorschlagen:',
   'Max. Dunning Level'          => 'höchste Mahnstufe',
   'Maximal amount difference'   => 'maximale Betragsabweichung',
@@ -1983,6 +1988,7 @@ $self->{texts} = {
   'Name does not make sense without any bsooqr options' => 'Option "Name in gewählten Belegen" wird ignoriert.',
   'Name in Selected Records'    => 'Name in gewählten Belegen',
   'Name of the goal/source (if field names remote_name and remote_name_1 exist they will be combined into field "remote_name")' => 'Name des Ziel- oder Quellkontos (wenn die Spalten remote_name und remote_name_1 existieren werden diese zu Feld "remote_name" zusammengefügt)',
+  'Need charge number!'         => 'Benötige Chargennummer!',
   'Negative reductions are possible to model price increases.' => 'Negative Abschläge sind möglich um Aufschläge zu modellieren.',
   'Neither sections nor function blocks have been created yet.' => 'Es wurden bisher weder Abschnitte noch Funktionsblöcke angelegt.',
   'Net Income Statement'        => 'Einnahmenüberschußrechnung',