From: Sven Schöling Date: Tue, 4 Jul 2017 12:52:27 +0000 (+0200) Subject: ShippedQty: Algorithmusdetails konfigurierbar machen X-Git-Tag: release-3.5.4~970 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=f559550ffb82e23f3712c11be4161d9c53a4b904;p=kivitendo-erp.git ShippedQty: Algorithmusdetails konfigurierbar machen --- diff --git a/SL/Controller/ClientConfig.pm b/SL/Controller/ClientConfig.pm index f85f10e38..237185d50 100644 --- a/SL/Controller/ClientConfig.pm +++ b/SL/Controller/ClientConfig.pm @@ -17,11 +17,12 @@ use SL::Locale::String qw(t8); use SL::PriceSource::ALL; use SL::Template; use SL::Controller::TopQuickSearch; +use SL::Helper::ShippedQty; __PACKAGE__->run_before('check_auth'); use Rose::Object::MakeMethods::Generic ( - 'scalar --get_set_init' => [ qw(defaults all_warehouses all_weightunits all_languages all_currencies all_templates all_price_sources h_unit_name available_quick_search_modules + 'scalar --get_set_init' => [ qw(defaults all_warehouses all_weightunits all_languages all_currencies all_templates all_price_sources h_unit_name available_quick_search_modules available_shipped_qty_item_identity_fields all_project_statuses all_project_types posting_options payment_options accounting_options inventory_options profit_options balance_startdate_method_options) ], ); @@ -197,6 +198,10 @@ sub init_available_quick_search_modules { [ SL::Controller::TopQuickSearch->new->available_modules ]; } +sub init_available_shipped_qty_item_identity_fields { + [ SL::Helper::ShippedQty->new->available_item_identity_fields ]; +} + # # filters # diff --git a/SL/DB/MetaSetup/Default.pm b/SL/DB/MetaSetup/Default.pm index 85954739d..f62f1d1fd 100644 --- a/SL/DB/MetaSetup/Default.pm +++ b/SL/DB/MetaSetup/Default.pm @@ -126,6 +126,9 @@ __PACKAGE__->meta->columns( sepa_creditor_id => { type => 'text' }, sepa_reference_add_vc_vc_id => { type => 'boolean', default => 'false' }, servicenumber => { type => 'text' }, + shipped_qty_fill_up => { type => 'boolean', default => 'true', not_null => 1 }, + shipped_qty_item_identity_fields => { type => 'array', default => '{parts_id}', not_null => 1 }, + shipped_qty_require_stock_out => { type => 'boolean', default => 'false', not_null => 1 }, show_bestbefore => { type => 'boolean', default => 'false' }, show_longdescription_select_item => { type => 'boolean', default => 'false' }, show_weight => { type => 'boolean', default => 'false', not_null => 1 }, diff --git a/SL/Helper/ShippedQty.pm b/SL/Helper/ShippedQty.pm index 38a450eb2..2054513de 100644 --- a/SL/Helper/ShippedQty.pm +++ b/SL/Helper/ShippedQty.pm @@ -9,6 +9,7 @@ use SL::DBUtils qw(selectall_hashref_query selectall_as_map); use List::Util qw(min); use List::MoreUtils qw(any all); use List::UtilsBy qw(partition_by); +use SL::Locale::String qw(t8); use Rose::Object::MakeMethods::Generic ( 'scalar' => [ qw(objects objects_or_ids shipped_qty ) ], @@ -86,6 +87,14 @@ my $oe_do_record_links = <<''; AND from_table = 'oe' AND to_table = 'delivery_orders' +my @known_item_identity_fields = qw(parts_id description reqdate serialnumber); +my %item_identity_fields = ( + parts_id => t8('Part'), + description => t8('Description'), + reqdate => t8('Reqdate'), + serialnumber => t8('Serial Number'), +); + sub calculate { my ($self, $data) = @_; @@ -244,6 +253,10 @@ sub normalize_input { $self->shipped_qty({}); } +sub available_item_identity_fields { + map { [ $_ => $item_identity_fields{$_} ] } @known_item_identity_fields; +} + sub init_oe_ids { my ($self) = @_; @@ -268,9 +281,9 @@ sub init_delivered { $d; } -sub init_require_stock_out { 0 } -sub init_item_identity_fields { [ qw(parts_id description reqdate serialnumber) ] } -sub init_fill_up { 1 } +sub init_require_stock_out { $::instance_conf->get_shipped_qty_require_stock_out } +sub init_item_identity_fields { [ grep $item_identity_fields{$_}, @{ $::instance_conf->get_shipped_qty_item_identity_fields } ] } +sub init_fill_up { $::instance_conf->get_shipped_qty_fill_up } 1; diff --git a/locale/de/all b/locale/de/all index eaaa2eba4..93190badf 100755 --- a/locale/de/all +++ b/locale/de/all @@ -359,6 +359,7 @@ $self->{texts} = { 'Automatically created invoice for fee and interest for dunning %s' => 'Automatisch erzeugte Rechnung für Gebühren und Zinsen zu Mahnung %s', 'Available' => 'Verfügbar', 'Available Prices' => 'Mögliche Preise', + 'Available identity fields' => 'Verfügbare Felder', 'Available qty' => 'Lagerbestand', 'BALANCE SHEET' => 'BILANZ', 'BB Balance' => 'Saldo Bank', @@ -1498,6 +1499,7 @@ $self->{texts} = { '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.', 'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' => 'Falls der oben genannte Datenbankbenutzer nicht die Berechtigung zum Anlegen neuer Datenbanken hat, so können Sie hier den Namen und das Passwort des Datenbankadministratoraccounts angeben:', 'If the default transfer out always succeed use this bin for negative stock quantity.' => 'Standardlagerplatz für Auslagern ohne Prüfung auf Bestand', + 'If yes, delivery order positions are considered "delivered" only if they have been stocked out of the inventory. Otherwise saving the delivery order is considered delivered.' => 'Wenn diese Option aktiviert ist, gelten Lieferscheinpositionen nur dann als geliefert wenn sie im Lieferschein ausgelagert wurden, und die Ware aus dem Lager ausgebucht wurde. Andernfalls gilt das Speichern des Lieferscheins als Lieferung.', 'If you enter values for the part number and / or part description then only those bins containing parts whose part number or part description match your input will be shown.' => 'Wenn Sie für die Artikelnummer und / oder die Beschreibung etwas eingeben, so werden nur die Lagerplätze angezeigt, in denen Waren eingelagert sind, die Ihre Suchbegriffe enthalten.', 'If you have not chosen for example the category revenue for a tax and you choose an revenue account to create a transfer in the general ledger, this tax will not be displayed in the tax dropdown.' => 'Wenn Sie z.B. die Kategory Erlös für eine Steuer nicht gewählt haben und ein Erlöskonto beim Erstellen einer Dialogbuchung wählen, wird diese Steuer auch nicht im Dropdown-Menü für die Steuern angezeigt.', 'If you lock the system normal users won\'t be able to log in.' => 'Wenn Sie das System sperren, so werden sich normale Benutzer nicht mehr anmelden können.', @@ -1693,6 +1695,7 @@ $self->{texts} = { 'Link to the following project:' => 'Mit dem folgenden Projekt verknüpfen:', 'Linked Records' => 'Verknüpfte Belege', 'Linked invoices' => 'Verknüpfte Rechnungen', + 'Linked positions will always reconciled first. If this is set to yes, unlinked positions will be reconciled in a second step. This is necessary in very old databases (with open delivery orders from before 3.4.0) and in businesses where delivery orders are frequently amended. Usually the direct links are faster and more accurate. Defaults to true for historical reasons only.' => 'Verlinkte Positionen werden immer zuerst abgeglichen. Wenn diese Option aktiviert ist, werden danach nicht verlinkte Lieferscheinpositionen mit den restlichen nicht vollständig gelieferten Auftragspositionen abgeglichen. Notwendig in alten Datenbanken (mit offenen Lieferscheinen von vor 3.4.0) und in Betrieben in denen Lieferscheine nachträglich ergänzt werden. In allen anderen Fällen ist es schneller und korrekter diese Methode zu deaktivieren. Die Voreinstellung auf "Ja" is aus Kompatibilitätsgründen.', 'Liquidity projection' => 'Liquiditätsübersicht', 'List Accounts' => 'Konten anzeigen', 'List Price' => 'Listenpreis', @@ -1988,6 +1991,7 @@ $self->{texts} = { 'One or more Perl modules missing' => 'Ein oder mehr Perl-Module fehlen', 'Onhand only sets the quantity in master data, not in inventory. This is only a legacy info field and will be overwritten as soon as a inventory transfer happens.' => 'Das Import-Feld Auf Lager setzt nur die Menge in den Stammdaten, nicht im Lagerbereich. Dies ist historisch gewachsen nur ein Informationsfeld was mit dem tatsächlichen Wert überschrieben wird, sobald eine wirkliche Lagerbewegung stattfindet (DB-Trigger).', 'Only Warnings and Errors' => 'Nur Warnungen und Fehler', + 'Only applies if the previous is set to true. When filling up unlinked positions, consider them matches if ALL of these fields match. For example, in a business with variants that are defined by special description, description needs to be part of the identity. If delivering several similar order positions by delivery date is common, reqdate should be included in the identity. Serialnumber is useful when the serialnumber in order and delivery order has to match.' => 'Ist nur relevant, wenn die vorherige Option angeschaltet ist. Zugewiesene Zeilen müssen in diesen Feldern identisch sein, und werden ansonsten als unterschiedlich behandelt. Wenn ein betried mit Varianten arbeitet, die in der Beschreibung kodiert sind, muss diese mit abgeglichen werden. Wenn Positionen mit Lieferdaten versehen werden, sollten diese mit abgeglichen werden. Seriennummer abzugleichen funktioniert nur, wenn diese in Auftrag und Lieferschein gepflegt werden.', 'Only booked accounts' => 'Nur bebuchte Konten', 'Only due follow-ups' => 'Nur fällige Wiedervorlagen', 'Only groups that have been configured for the client the user logs in to will be considered.' => 'Allerdings werden nur diejenigen Gruppen herangezogen, die für den Mandanten konfiguriert sind.', @@ -2177,6 +2181,7 @@ $self->{texts} = { 'Poland' => 'Polen', 'Port' => 'Port', 'Portrait' => 'Hochformat', + 'Position identity fields for fill up?' => 'Felder, die für Abgleich übereinstimmen müssen?', 'Position type in quotation/order' => 'Positionstyp in Angebot/Auftrag', 'Post' => 'Buchen', 'Post Payment' => 'Zahlung buchen', @@ -2429,6 +2434,7 @@ $self->{texts} = { 'Requested execution date to' => 'Gewünschtes Ausführungsdatum bis', 'Requests for Quotation' => 'Preisanfragen', 'Require a transaction description in purchase and sales records' => 'Vorgangsbezeichnung in Einkaufs- und Verkaufsbelegen erzwingen', + 'Require stock out to consider a delivery order position delivered?' => 'Muss eine Lieferscheinposition ausgelagert sein um als geliefert zu gelten?', 'Required by' => 'Lieferdatum', 'Requirement Spec Status' => 'Pflichtenheftstatus', 'Requirement Spec Statuses' => 'Pflichtenheftstatus', @@ -2592,6 +2598,7 @@ $self->{texts} = { 'Select type of transfer' => 'Grund der Umlagerung auswählen', 'Select type of transfer in' => 'Grund der Einlagerung auswählen:', 'Selected' => 'Ausgewählt', + 'Selected identity fields' => 'Ausgewählte Felder', 'Selection' => 'Auswahlbox', 'Selection fields: The option field must contain the available options for the selection. Options are separated by \'##\', for example \'Early##Normal##Late\'.' => 'Auswahlboxen: Das Optionenfeld muss die für die Auswahl verfügbaren Einträge enthalten. Die Einträge werden mit \'##\' voneinander getrennt. Beispiel: \'Früh##Normal##Spät\'.', 'Sell Price' => 'Verkaufspreis', @@ -2636,6 +2643,7 @@ $self->{texts} = { 'Setup Menu' => 'Menü-Variante', 'Ship to (database ID)' => 'Lieferadresse (Datenbank-ID)', 'Ship via' => 'Transportmittel', + 'Shipped Quantity Algorithm' => 'Liefermengen Berechnung', 'Shipping Address' => 'Lieferadresse', 'Shipping Point' => 'Versandort', 'Shipping address (name)' => 'Name der Lieferadresse', @@ -3446,6 +3454,7 @@ $self->{texts} = { 'Use default booking group because wanted is missing' => 'Fehlende Buchungsgruppe, deshalb Standardbuchungsgruppe', 'Use default warehouse for assembly transfer' => 'Zum Fertigen Standardlager des Bestandteils verwenden', 'Use existing templates' => 'Vorhandene Druckvorlagen verwenden', + 'Use fill up when calculating shipped quantitiies?' => 'Sollen nicht verlinkte Positionen abgeglichen werden?', 'Use linked items' => 'Verknüpfte Positionen verwenden', 'Use master default bin for Default Transfer, if no default bin for the part is configured' => 'Standardlagerplatz für Ein- / Auslagern über Standard-Lagerplatz, falls für die Ware kein expliziter Lagerplatz konfiguriert ist', 'Use this storage backend for all generated PDF-Files' => 'Verwende dieses Backend für generierte PDF-Dateien', diff --git a/sql/Pg-upgrade2/get_shipped_qty_config.sql b/sql/Pg-upgrade2/get_shipped_qty_config.sql new file mode 100644 index 000000000..5c1e1d67a --- /dev/null +++ b/sql/Pg-upgrade2/get_shipped_qty_config.sql @@ -0,0 +1,10 @@ +-- @tag: get_shipped_qty_config +-- @description: Mandantenweite Konfiguration für das Verhalten von Liefermengenabgleich +-- @depends: release_3_4_1 +-- @encoding: utf-8 + +ALTER TABLE defaults ADD COLUMN shipped_qty_require_stock_out BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE defaults ADD COLUMN shipped_qty_fill_up BOOLEAN NOT NULL DEFAULT TRUE; +ALTER TABLE defaults ADD COLUMN shipped_qty_item_identity_fields TEXT[] NOT NULL DEFAULT '{parts_id}'; + + diff --git a/templates/webpages/client_config/_features.html b/templates/webpages/client_config/_features.html index 9c9051583..225859ad8 100644 --- a/templates/webpages/client_config/_features.html +++ b/templates/webpages/client_config/_features.html @@ -302,5 +302,25 @@ [% LxERP.t8('Experimental features are:') %] [% LxERP.t8('new order controller') %], [% LxERP.t8('Assortment') %] - + [% LxERP.t8("Shipped Quantity Algorithm") %] + + [% LxERP.t8('Require stock out to consider a delivery order position delivered?') %] + [% L.yes_no_tag('defaults.shipped_qty_require_stock_out', SELF.defaults.shipped_qty_require_stock_out) %] + [% LxERP.t8('If yes, delivery order positions are considered "delivered" only if they have been stocked out of the inventory. Otherwise saving the delivery order is considered delivered.') %] + + + [% LxERP.t8('Use fill up when calculating shipped quantitiies?') %] + [% L.yes_no_tag('defaults.shipped_qty_fill_up', SELF.defaults.shipped_qty_fill_up) %] + [% LxERP.t8('Linked positions will always reconciled first. If this is set to yes, unlinked positions will be reconciled in a second step. This is necessary in very old databases (with open delivery orders from before 3.4.0) and in businesses where delivery orders are frequently amended. Usually the direct links are faster and more accurate. Defaults to true for historical reasons only.') %] + + + [% LxERP.t8('Position identity fields for fill up?') %] + + [% L.select_tag("defaults.shipped_qty_item_identity_fields[]", SELF.available_shipped_qty_item_identity_fields, id="defaults_shipped_qty_item_identity_fields", multiple=1, default=SELF.defaults.shipped_qty_item_identity_fields) %] + [% L.multiselect2side("defaults_shipped_qty_item_identity_fields", labelsx=LxERP.t8("Available identity fields"), labeldx=LxERP.t8("Selected identity fields")) %] + [% LxERP.t8('Only applies if the previous is set to true. When filling up unlinked positions, consider them matches if ALL of these fields match. For example, in a business with variants that are defined by special description, description needs to be part of the identity. If delivering several similar order positions by delivery date is common, reqdate should be included in the identity. Serialnumber is useful when the serialnumber in order and delivery order has to match.') %] + + + +