Projekt

Allgemein

Profil

Herunterladen (48,5 KB) Statistiken
| Zweig: | Markierung: | Revision:
83914eeb Moritz Bunkus
#====================================================================
# LX-Office ERP
# Copyright (C) 2004
# Based on SQL-Ledger Version 2.1.9
# Web http://www.lx-office.org
#
#=====================================================================
# SQL-Ledger Accounting
# Copyright (C) 1999-2003
#
# Author: Dieter Simader
# Email: dsimader@sql-ledger.org
# Web: http://www.sql-ledger.org
#
# Contributors:
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
f7b15d43 Christian Wittmer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1335, USA.
83914eeb Moritz Bunkus
#======================================================================
#
# Warehouse module
#
#======================================================================

package WH;

use SL::AM;
use SL::DBUtils;
use SL::Form;
d16c1b3c Moritz Bunkus
use SL::Util qw(trim);
c510d88b Sven Schöling
4f63ea87 Geoffrey Richardson
use warnings;
c510d88b Sven Schöling
use strict;

83914eeb Moritz Bunkus
sub transfer {
0ffc7ab0 Sven Schöling
$::lxdebug->enter_sub;
83914eeb Moritz Bunkus
0ffc7ab0 Sven Schöling
my ($self, @args) = @_;
83914eeb Moritz Bunkus
0ffc7ab0 Sven Schöling
if (!@args) {
$::lxdebug->leave_sub;
83914eeb Moritz Bunkus
return;
}

0ffc7ab0 Sven Schöling
require SL::DB::TransferType;
require SL::DB::Part;
require SL::DB::Employee;
require SL::DB::Inventory;
c7cabbb2 Sven Schöling
4bd1e2f8 Sven Schöling
my $employee = SL::DB::Manager::Employee->find_by(login => $::myconfig{login});
0ffc7ab0 Sven Schöling
my ($now) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT current_date|);
my @directions = (undef, qw(out in transfer));
83914eeb Moritz Bunkus
c7cabbb2 Sven Schöling
my $objectify = sub {
my ($transfer, $field, $class, @find_by) = @_;
83914eeb Moritz Bunkus
c7cabbb2 Sven Schöling
@find_by = (description => $transfer->{$field}) unless @find_by;
83914eeb Moritz Bunkus
c7cabbb2 Sven Schöling
if ($transfer->{$field} || $transfer->{"${field}_id"}) {
return ref $transfer->{$field} && $transfer->{$field}->isa($class) ? $transfer->{$field}
: $transfer->{$field} ? $class->_get_manager_class->find_by(@find_by)
: $class->_get_manager_class->find_by(id => $transfer->{"${field}_id"});
83914eeb Moritz Bunkus
}
c7cabbb2 Sven Schöling
return;
};

96a42a5a Sven Schöling
my @trans_ids;

9d14fd7e Sven Schöling
my $db = SL::DB::Inventory->new->db;
40ebe601 Bernd Bleßmann
$db->with_transaction(sub{
9d14fd7e Sven Schöling
while (my $transfer = shift @args) {
1bbcb32c Bernd Bleßmann
my $trans_id;
($trans_id) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT nextval('id')|) if $transfer->{qty};
c7cabbb2 Sven Schöling
my $part = $objectify->($transfer, 'parts', 'SL::DB::Part');
17718a5a Moritz Bunkus
my $unit = $objectify->($transfer, 'unit', 'SL::DB::Unit', name => $transfer->{unit});
c7cabbb2 Sven Schöling
my $qty = $transfer->{qty};
my $src_bin = $objectify->($transfer, 'src_bin', 'SL::DB::Bin');
my $dst_bin = $objectify->($transfer, 'dst_bin', 'SL::DB::Bin');
my $src_wh = $objectify->($transfer, 'src_warehouse', 'SL::DB::Warehouse');
my $dst_wh = $objectify->($transfer, 'dst_warehouse', 'SL::DB::Warehouse');
my $project = $objectify->($transfer, 'project', 'SL::DB::Project');

$src_wh ||= $src_bin->warehouse if $src_bin;
$dst_wh ||= $dst_bin->warehouse if $dst_bin;

my $direction = 0; # bit mask
$direction |= 1 if $src_bin;
$direction |= 2 if $dst_bin;

e9d624e6 Frank Messerschmidt
my $transfer_type_id;
if ($transfer->{transfer_type_id}) {
$transfer_type_id = $transfer->{transfer_type_id};
} else {
my $transfer_type = $objectify->($transfer, 'transfer_type', 'SL::DB::TransferType', direction => $directions[$direction],
description => $transfer->{transfer_type});
$transfer_type_id = $transfer_type->id;
}
c7cabbb2 Sven Schöling
1bbcb32c Bernd Bleßmann
my $stocktaking_qty = $transfer->{stocktaking_qty};

c7cabbb2 Sven Schöling
my %params = (
part => $part,
employee => $employee,
e9d624e6 Frank Messerschmidt
trans_type_id => $transfer_type_id,
c7cabbb2 Sven Schöling
project => $project,
trans_id => $trans_id,
shippingdate => !$transfer->{shippingdate} || $transfer->{shippingdate} eq 'current_date'
? $now : $transfer->{shippingdate},
9d07c34f Bernd Bleßmann
map { $_ => $transfer->{$_} } qw(chargenumber bestbefore oe_id delivery_order_items_stock_id invoice_id comment),
c7cabbb2 Sven Schöling
);

if ($unit) {
1bbcb32c Bernd Bleßmann
$qty = $unit->convert_to($qty, $part->unit_obj);
$stocktaking_qty = $unit->convert_to($stocktaking_qty, $part->unit_obj);
c7cabbb2 Sven Schöling
}
83914eeb Moritz Bunkus
3fa948b4 Sven Schöling
$params{chargenumber} ||= '';

1bbcb32c Bernd Bleßmann
my @inventories;
if ($qty && $direction & 1) {
push @inventories, SL::DB::Inventory->new(
c7cabbb2 Sven Schöling
%params,
warehouse => $src_wh,
bin => $src_bin,
qty => $qty * -1,
)->save;
}
83914eeb Moritz Bunkus
1bbcb32c Bernd Bleßmann
if ($qty && $direction & 2) {
push @inventories, SL::DB::Inventory->new(
c7cabbb2 Sven Schöling
%params,
warehouse => $dst_wh->id,
bin => $dst_bin->id,
qty => $qty,
)->save;
5a923b79 Jan Büren
# Standardlagerplatz in Stammdaten gleich mitverschieben
if (defined($transfer->{change_default_bin})){
791090f3 Jan Büren
$part->update_attributes(warehouse_id => $dst_wh->id, bin_id => $dst_bin->id);
5a923b79 Jan Büren
}
40ebe601 Bernd Bleßmann
}
96a42a5a Sven Schöling
1bbcb32c Bernd Bleßmann
# Record stocktaking if requested.
# This is only possible if transfer was a stock in or stock out,
# but not both (transfer).
if ($transfer->{record_stocktaking}) {
die 'Stocktaking can only be recorded for stock in or stock out, but not on a transfer.' if scalar @inventories > 1;

my $inventory_id;
$inventory_id = $inventories[0]->id if $inventories[0];

SL::DB::Stocktaking->new(
inventory_id => $inventory_id,
warehouse => $src_wh || $dst_wh,
bin => $src_bin || $dst_bin,
parts_id => $part->id,
employee_id => $employee->id,
qty => $stocktaking_qty,
comment => $transfer->{comment},
cutoff_date => $transfer->{stocktaking_cutoff_date},
chargenumber => $transfer->{chargenumber},
bestbefore => $transfer->{bestbefore},
)->save;

}

96a42a5a Sven Schöling
push @trans_ids, $trans_id;
83914eeb Moritz Bunkus
}
40ebe601 Bernd Bleßmann
1;
9d14fd7e Sven Schöling
}) or do {
$::form->error("Warehouse transfer error: " . join("\n", (split(/\n/, $db->error))[0..2]));
c7cabbb2 Sven Schöling
};
83914eeb Moritz Bunkus
0ffc7ab0 Sven Schöling
$::lxdebug->leave_sub;
96a42a5a Sven Schöling
return @trans_ids;
83914eeb Moritz Bunkus
}
4f63ea87 Geoffrey Richardson
sub transfer_assembly {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;
096f9e3e Bernd Bleßmann
Common::check_params(\%params, qw(assembly_id dst_warehouse_id login qty unit dst_bin_id chargenumber bestbefore comment));
4f63ea87 Geoffrey Richardson
# my $maxcreate=WH->check_assembly_max_create(assembly_id =>$params{'assembly_id'}, dbh => $my_dbh);

my $myconfig = \%main::myconfig;
my $form = $main::form;
4236c151 Jan Büren
my $kannNichtFertigen =""; # Falls leer dann erfolgreich
471af251 Sven Schöling
SL::DB->client->with_transaction(sub {
my $dbh = $params{dbh} || SL::DB->client->dbh;

# Ablauferklärung
#
# ... Standard-Check oben Ende. Hier die eigentliche SQL-Abfrage
# select parts_id,qty from assembly where id=1064;
# Erweiterung für bug 935 am 23.4.09 -
# Erzeugnisse können Dienstleistungen enthalten, die ja nicht 'lagerbar' sind.
# select parts_id,qty from assembly inner join parts on assembly.parts_id = parts.id
# where assembly.id=1066 and inventory_accno_id IS NOT NULL;
#
# Erweiterung für bug 23.4.09 -2 Erzeugnisse in Erzeugnissen können nicht ausgelagert werden,
# wenn assembly nicht überprüft wird ...
# patch von joachim eingespielt 24.4.2009:
# my $query = qq|select parts_id,qty from assembly inner join parts
# on assembly.parts_id = parts.id where assembly.id = ? and
# (inventory_accno_id IS NOT NULL or parts.assembly = TRUE)|;

# Lager in dem die Bestandteile gesucht werden kann entweder das Ziellager sein oder ist per Mandantenkonfig
# auf das Standardlager des Bestandteiles schaltbar

my $use_default_warehouse = $::instance_conf->get_transfer_default_warehouse_for_assembly;

my $query = qq|SELECT assembly.parts_id, assembly.qty, parts.warehouse_id
FROM assembly INNER JOIN parts ON assembly.parts_id = parts.id
98b64fe1 Geoffrey Richardson
WHERE assembly.id = ? AND parts.part_type != 'service'|;
471af251 Sven Schöling
my $sth_part_qty_assembly = prepare_execute_query($form, $dbh, $query, $params{assembly_id});

# Hier wird das prepared Statement für die Schleife über alle Lagerplätze vorbereitet
my $transferPartSQL = qq|INSERT INTO inventory (parts_id, warehouse_id, bin_id, chargenumber, bestbefore, comment, employee_id, qty,
trans_id, trans_type_id, shippingdate)
VALUES (?, ?, ?, ?, ?, ?, (SELECT id FROM employee WHERE login = ?), ?, nextval('id'),
(SELECT id FROM transfer_type WHERE direction = 'out' AND description = 'used'),
(SELECT current_date))|;
my $sthTransferPartSQL = prepare_query($form, $dbh, $transferPartSQL);

# der return-string für die fehlermeldung inkl. welche waren zum fertigen noch fehlen

my $schleife_durchlaufen=0; # Falls die Schleife nicht ausgeführt wird -> Keine Einzelteile definiert. Bessere Idee? jan
while (my $hash_ref = $sth_part_qty_assembly->fetchrow_hashref()) { #Schleife für select parts_id,(...) from assembly
$schleife_durchlaufen=1; # Erzeugnis definiert

my $partsQTY = $hash_ref->{qty} * $params{qty}; # benötigte teile * anzahl erzeugnisse
my $currentPart_ID = $hash_ref->{parts_id};

my $currentPart_WH_ID = $use_default_warehouse && $hash_ref->{warehouse_id} ? $hash_ref->{warehouse_id} : $params{dst_warehouse_id};
my $no_check = 0;

# Prüfen ob Erzeugnis-Teile Standardlager haben.
if ($use_default_warehouse && ! $hash_ref->{warehouse_id}) {
# Prüfen ob in Mandantenkonfiguration ein Standardlager aktiviert isti.
if ($::instance_conf->get_transfer_default_ignore_onhand) {
$currentPart_WH_ID = $::instance_conf->get_warehouse_id_ignore_onhand;
$no_check = 1;
} else {
$kannNichtFertigen .= "Kein Standardlager: " .
" Die Ware " . $self->get_part_description(parts_id => $currentPart_ID) .
" hat kein Standardlager definiert " .
", um das Erzeugnis herzustellen. <br>";
next;
}
3160b088 Martin Helmling
}
471af251 Sven Schöling
my $warehouse_info = $self->get_basic_warehouse_info('id'=> $currentPart_WH_ID);
my $warehouse_desc = $warehouse_info->{"warehouse_description"};

# Fertigen ohne Prüfung nach Bestand
if ($no_check) {
my $temppart_bin_id = $::instance_conf->get_bin_id_ignore_onhand;
my $temppart_chargenumber = "";
my $temppart_bestbefore = localtime();
my $temppart_qty = $partsQTY * -1;
4f63ea87 Geoffrey Richardson
0f214df5 Waldemar Toews
do_statement($form, $sthTransferPartSQL, $transferPartSQL, $currentPart_ID, $currentPart_WH_ID,
471af251 Sven Schöling
$temppart_bin_id, $temppart_chargenumber, $temppart_bestbefore, 'Verbraucht für ' .
$self->get_part_description(parts_id => $params{assembly_id}), $params{login}, $temppart_qty);
next;
}
# Überprüfen, ob diese Anzahl gefertigt werden kann
my $max_parts = $self->get_max_qty_parts(parts_id => $currentPart_ID, # $self->method() == this.method()
warehouse_id => $currentPart_WH_ID);

if ($partsQTY > $max_parts){
# Gibt es hier ein Problem mit nicht "escapten" Zeichen?
# 25.4.09 Antwort: Ja. Aber erst wenn im Frontend die locales-Funktion aufgerufen wird

$kannNichtFertigen .= "Zum Fertigen fehlen: " . abs($partsQTY - $max_parts) .
" Einheiten der Ware: " . $self->get_part_description(parts_id => $currentPart_ID) .
" im Lager: " . $warehouse_desc .
", um das Erzeugnis herzustellen. <br>"; # Konnte die Menge nicht mit der aktuellen Anzahl der Waren fertigen
next; # die weiteren Überprüfungen sind unnötig, daher das nächste elemente prüfen (genaue Ausgabe, was noch fehlt)
4f63ea87 Geoffrey Richardson
}

471af251 Sven Schöling
# Eine kurze Vorabfrage, um den Lagerplatz, Chargennummer und die Mindesthaltbarkeit zu bestimmen
# Offen: Die Summe über alle Lagerplätze wird noch nicht gebildet
# Gelöst: Wir haben vorher schon die Abfrage durchgeführt, ob wir fertigen können.
# Noch besser gelöst: Wir laufen durch alle benötigten Waren zum Fertigen und geben eine Rückmeldung an den Benutzer was noch fehlt
# und lösen den Rest dann so wie bei xplace im Barcode-Programm
# S.a. Kommentar im bin/mozilla-Code mb übernimmt und macht das in ordentlich

my $tempquery = qq|SELECT SUM(qty), bin_id, chargenumber, bestbefore FROM inventory
WHERE warehouse_id = ? AND parts_id = ? GROUP BY bin_id, chargenumber, bestbefore having SUM(qty)>0|;
my $tempsth = prepare_execute_query($form, $dbh, $tempquery, $currentPart_WH_ID, $currentPart_ID);

# Alle Werte zu dem einzelnen Artikel, die wir später auslagern
my $tmpPartsQTY = $partsQTY;

while (my $temphash_ref = $tempsth->fetchrow_hashref()) {
my $temppart_bin_id = $temphash_ref->{bin_id}; # kann man hier den quelllagerplatz beim verbauen angeben?
my $temppart_chargenumber = $temphash_ref->{chargenumber};
my $temppart_bestbefore = conv_date($temphash_ref->{bestbefore});
my $temppart_qty = $temphash_ref->{sum};

if ($tmpPartsQTY > $temppart_qty) { # wir haben noch mehr waren zum wegbuchen.
# Wir buchen den kompletten Lagerplatzbestand und zählen die Hilfsvariable runter
$tmpPartsQTY = $tmpPartsQTY - $temppart_qty;
$temppart_qty = $temppart_qty * -1; # TODO beim analyiseren des sql-trace, war dieser wert positiv,
# wenn * -1 als berechnung in der parameter-übergabe angegeben wird.
# Dieser Wert IST und BLEIBT positiv!! Hilfe.
# Liegt das daran, dass dieser Wert aus einem SQL-Statement stammt?
do_statement($form, $sthTransferPartSQL, $transferPartSQL, $currentPart_ID, $currentPart_WH_ID,
$temppart_bin_id, $temppart_chargenumber, $temppart_bestbefore, 'Verbraucht für ' .
$self->get_part_description(parts_id => $params{assembly_id}), $params{login}, $temppart_qty);

# hier ist noch ein fehler am besten mit definierten erzeugnissen debuggen 02/2009 jb
# idee: ausbuch algorithmus mit rekursion lösen und an- und abschaltbar machen
# das problem könnte sein, dass strict nicht an war und sth global eine andere zuweisung bekam
# auf jeden fall war der internal-server-error nach aktivierung von strict und warnings plus ein paar my-definitionen weg
} else { # okay, wir haben weniger oder gleich Waren die wir wegbuchen müssen, wir können also aufhören
$tmpPartsQTY *=-1;
do_statement($form, $sthTransferPartSQL, $transferPartSQL, $currentPart_ID, $currentPart_WH_ID,
$temppart_bin_id, $temppart_chargenumber, $temppart_bestbefore, 'Verbraucht für ' .
$self->get_part_description(parts_id => $params{assembly_id}), $params{login}, $tmpPartsQTY);
last; # beendet die schleife (springt zum letzten element)
}
} # ende while SELECT SUM(qty), bin_id, chargenumber, bestbefore FROM inventory WHERE warehouse_id
} #ende while select parts_id,qty from assembly where id = ?

if ($schleife_durchlaufen==0){ # falls die schleife nicht durchlaufen wurde, wurden auch
# keine einzelteile definiert
$kannNichtFertigen ="Für dieses Erzeugnis sind keine Einzelteile definiert.
Dementsprechend kann auch nichts hergestellt werden";
}
# gibt die Fehlermeldung zurück. A.) Keine Teile definiert
# B.) Artikel und Anzahl der fehlenden Teile/Dienstleistungen
7ccc2fd5 Jan Büren
die "<br><br>" . $kannNichtFertigen if ($kannNichtFertigen);
471af251 Sven Schöling
# soweit alles gut. Jetzt noch die wirkliche Lagerbewegung für das Erzeugnis ausführen ...
my $transferAssemblySQL = qq|INSERT INTO inventory (parts_id, warehouse_id, bin_id, chargenumber, bestbefore,
comment, employee_id, qty, trans_id, trans_type_id, shippingdate)
VALUES (?, ?, ?, ?, ?, ?, (SELECT id FROM employee WHERE login = ?), ?, nextval('id'),
(SELECT id FROM transfer_type WHERE direction = 'in' AND description = 'assembled'),
(select current_date))|;
my $sthTransferAssemblySQL = prepare_query($form, $dbh, $transferAssemblySQL);
do_statement($form, $sthTransferAssemblySQL, $transferAssemblySQL, $params{assembly_id}, $params{dst_warehouse_id},
$params{dst_bin_id}, $params{chargenumber}, conv_date($params{bestbefore}), $params{comment}, $params{login}, $params{qty});
1;
}) or do { return $kannNichtFertigen };
4f63ea87 Geoffrey Richardson
$main::lxdebug->leave_sub();
c09536f4 Sven Schöling
return 1; # Alles erfolgreich
4f63ea87 Geoffrey Richardson
}
83914eeb Moritz Bunkus
sub get_warehouse_journal {
$main::lxdebug->enter_sub();

my $self = shift;
my %filter = @_;

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $all_units = AM->retrieve_units($myconfig, $form);

# connect to database
my $dbh = $form->get_standard_dbh($myconfig);

# filters
c510d88b Sven Schöling
my (@filter_ary, @filter_vars, $joins, %select_tokens, %select);
83914eeb Moritz Bunkus
72f25b53 Bernd Bleßmann
if ($filter{warehouse_id}) {
83914eeb Moritz Bunkus
push @filter_ary, "w1.id = ? OR w2.id = ?";
push @filter_vars, $filter{warehouse_id}, $filter{warehouse_id};
}

72f25b53 Bernd Bleßmann
if ($filter{bin_id}) {
83914eeb Moritz Bunkus
push @filter_ary, "b1.id = ? OR b2.id = ?";
push @filter_vars, $filter{bin_id}, $filter{bin_id};
}

if ($filter{partnumber}) {
push @filter_ary, "p.partnumber ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{partnumber});
83914eeb Moritz Bunkus
}

if ($filter{description}) {
push @filter_ary, "(p.description ILIKE ?)";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{description});
83914eeb Moritz Bunkus
}

65d2537d Martin Helmling
if ($filter{classification_id}) {
push @filter_ary, "p.classification_id = ?";
push @filter_vars, $filter{classification_id};
}

83914eeb Moritz Bunkus
if ($filter{chargenumber}) {
ddf943f2 Moritz Bunkus
push @filter_ary, "i1.chargenumber ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{chargenumber});
83914eeb Moritz Bunkus
}

d16c1b3c Moritz Bunkus
if (trim($form->{bestbefore})) {
096f9e3e Bernd Bleßmann
push @filter_ary, "?::DATE = i1.bestbefore::DATE";
d16c1b3c Moritz Bunkus
push @filter_vars, trim($form->{bestbefore});
096f9e3e Bernd Bleßmann
}

d16c1b3c Moritz Bunkus
if (trim($form->{fromdate})) {
8fd88684 Geoffrey Richardson
push @filter_ary, "? <= i1.shippingdate";
d16c1b3c Moritz Bunkus
push @filter_vars, trim($form->{fromdate});
83914eeb Moritz Bunkus
}

d16c1b3c Moritz Bunkus
if (trim($form->{todate})) {
8fd88684 Geoffrey Richardson
push @filter_ary, "? >= i1.shippingdate";
d16c1b3c Moritz Bunkus
push @filter_vars, trim($form->{todate});
83914eeb Moritz Bunkus
}

if ($form->{l_employee}) {
$joins .= "";
}

# prepare qty comparison for later filtering
my ($f_qty_op, $f_qty, $f_qty_base_unit);
if ($filter{qty_op} && defined($filter{qty}) && $filter{qty_unit} && $all_units->{$filter{qty_unit}}) {
$f_qty_op = $filter{qty_op};
$f_qty = $filter{qty} * $all_units->{$filter{qty_unit}}->{factor};
$f_qty_base_unit = $all_units->{$filter{qty_unit}}->{base_unit};
}

map { $_ = "(${_})"; } @filter_ary;

# if of a property number or description is requested,
# automatically check the matching id too.
map { $form->{"l_${_}id"} = "Y" if ($form->{"l_${_}description"} || $form->{"l_${_}number"}); } qw(warehouse bin);

# customize shown entry for not available fields.
$filter{na} = '-' unless $filter{na};

# make order, search in $filter and $form
19688fca Moritz Bunkus
my $sort_col = $form->{sort};
my $sort_order = $form->{order};

$sort_col = $filter{sort} unless $sort_col;
3c938e03 Martin Helmling
$sort_col = 'shippingdate' if $sort_col eq 'date';
8fd88684 Geoffrey Richardson
$sort_order = ($sort_col = 'shippingdate') unless $sort_col;
3c938e03 Martin Helmling
my %orderspecs = (
'shippingdate' => ['shippingdate', 'r_itime', 'r_parts_id'],
'bin_to' => ['bin_to', 'r_itime', 'r_parts_id'],
'bin_from' => ['bin_from', 'r_itime', 'r_parts_id'],
'warehouse_to' => ['warehouse_to, r_itime, r_parts_id'],
'warehouse_from' => ['warehouse_from, r_itime, r_parts_id'],
'partnumber' => ['partnumber'],
'partdescription'=> ['partdescription'],
'partunit' => ['partunit, r_itime, r_parts_id'],
'qty' => ['qty, r_itime, r_parts_id'],
'oe_id' => ['oe_id'],
'comment' => ['comment'],
'trans_type' => ['trans_type'],
'employee' => ['employee'],
'projectnumber' => ['projectnumber'],
4c01b1b2 Martin Helmling
'chargenumber' => ['chargenumber'],
3c938e03 Martin Helmling
);

$sort_order = $filter{order} unless $sort_order;
my $ASC = ($sort_order ? " DESC" : " ASC");
my $sort_spec = join("$ASC , ", @{$orderspecs{$sort_col}}). " $ASC";
83914eeb Moritz Bunkus
74fca575 Sven Schöling
my $where_clause = @filter_ary ? join(" AND ", @filter_ary) . " AND " : '';
83914eeb Moritz Bunkus
$select_tokens{'trans'} = {
"parts_id" => "i1.parts_id",
"qty" => "ABS(SUM(i1.qty))",
"partnumber" => "p.partnumber",
"partdescription" => "p.description",
65d2537d Martin Helmling
"classification_id" => "p.classification_id",
"part_type" => "p.part_type",
83914eeb Moritz Bunkus
"bindescription" => "b.description",
"chargenumber" => "i1.chargenumber",
096f9e3e Bernd Bleßmann
"bestbefore" => "i1.bestbefore",
83914eeb Moritz Bunkus
"warehousedescription" => "w.description",
"partunit" => "p.unit",
"bin_from" => "b1.description",
"bin_to" => "b2.description",
"warehouse_from" => "w1.description",
"warehouse_to" => "w2.description",
"comment" => "i1.comment",
"trans_type" => "tt.description",
"trans_id" => "i1.trans_id",
17409513 Moritz Bunkus
"oe_id" => "COALESCE(i1.oe_id, i2.oe_id)",
6d808fff Bernd Bleßmann
"invoice_id" => "COALESCE(i1.invoice_id, i2.invoice_id)",
8fd88684 Geoffrey Richardson
"date" => "i1.shippingdate",
83914eeb Moritz Bunkus
"itime" => "i1.itime",
8fd88684 Geoffrey Richardson
"shippingdate" => "i1.shippingdate",
83914eeb Moritz Bunkus
"employee" => "e.name",
"projectnumber" => "COALESCE(pr.projectnumber, '$filter{na}')",
};

$select_tokens{'out'} = {
"bin_to" => "'$filter{na}'",
"warehouse_to" => "'$filter{na}'",
};

$select_tokens{'in'} = {
"bin_from" => "'$filter{na}'",
"warehouse_from" => "'$filter{na}'",
};

65d2537d Martin Helmling
$form->{l_classification_id} = 'Y';
3c938e03 Martin Helmling
$form->{l_part_type} = 'Y';
$form->{l_itime} = 'Y';
6d808fff Bernd Bleßmann
$form->{l_invoice_id} = $form->{l_oe_id} if $form->{l_oe_id};

83914eeb Moritz Bunkus
# build the select clauses.
# take all the requested ones from the first hash and overwrite them from the out/in hashes if present.
for my $i ('trans', 'out', 'in') {
4895d0c6 Moritz Bunkus
$select{$i} = join ', ', map { +/^l_/; ($select_tokens{$i}{"$'"} || $select_tokens{'trans'}{"$'"}) . " AS r_$'" }
8fd88684 Geoffrey Richardson
( grep( { !/qty$/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form), qw(l_parts_id l_qty l_partunit l_shippingdate) );
83914eeb Moritz Bunkus
}

my $group_clause = join ", ", map { +/^l_/; "r_$'" }
3c938e03 Martin Helmling
( grep( { !/qty$/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form), qw(l_parts_id l_partunit l_shippingdate l_itime) );
83914eeb Moritz Bunkus
72f25b53 Bernd Bleßmann
$where_clause = defined($where_clause) ? $where_clause : '';
8fd88684 Geoffrey Richardson
83914eeb Moritz Bunkus
my $query =
3c938e03 Martin Helmling
qq|SELECT * FROM (SELECT DISTINCT $select{trans}
83914eeb Moritz Bunkus
FROM inventory i1
LEFT JOIN inventory i2 ON i1.trans_id = i2.trans_id
LEFT JOIN parts p ON i1.parts_id = p.id
LEFT JOIN bin b1 ON i1.bin_id = b1.id
LEFT JOIN bin b2 ON i2.bin_id = b2.id
LEFT JOIN warehouse w1 ON i1.warehouse_id = w1.id
LEFT JOIN warehouse w2 ON i2.warehouse_id = w2.id
LEFT JOIN transfer_type tt ON i1.trans_type_id = tt.id
LEFT JOIN project pr ON i1.project_id = pr.id
LEFT JOIN employee e ON i1.employee_id = e.id
WHERE $where_clause i2.qty = -i1.qty AND i2.qty > 0 AND
i1.trans_id IN ( SELECT i.trans_id FROM inventory i GROUP BY i.trans_id HAVING COUNT(i.trans_id) = 2 )
GROUP BY $group_clause

UNION

SELECT DISTINCT $select{out}
FROM inventory i1
LEFT JOIN inventory i2 ON i1.trans_id = i2.trans_id
LEFT JOIN parts p ON i1.parts_id = p.id
LEFT JOIN bin b1 ON i1.bin_id = b1.id
LEFT JOIN bin b2 ON i2.bin_id = b2.id
LEFT JOIN warehouse w1 ON i1.warehouse_id = w1.id
LEFT JOIN warehouse w2 ON i2.warehouse_id = w2.id
LEFT JOIN transfer_type tt ON i1.trans_type_id = tt.id
LEFT JOIN project pr ON i1.project_id = pr.id
LEFT JOIN employee e ON i1.employee_id = e.id
WHERE $where_clause i1.qty < 0 AND
i1.trans_id IN ( SELECT i.trans_id FROM inventory i GROUP BY i.trans_id HAVING COUNT(i.trans_id) = 1 )
GROUP BY $group_clause

UNION

SELECT DISTINCT $select{in}
FROM inventory i1
LEFT JOIN inventory i2 ON i1.trans_id = i2.trans_id
LEFT JOIN parts p ON i1.parts_id = p.id
LEFT JOIN bin b1 ON i1.bin_id = b1.id
LEFT JOIN bin b2 ON i2.bin_id = b2.id
LEFT JOIN warehouse w1 ON i1.warehouse_id = w1.id
LEFT JOIN warehouse w2 ON i2.warehouse_id = w2.id
LEFT JOIN transfer_type tt ON i1.trans_type_id = tt.id
LEFT JOIN project pr ON i1.project_id = pr.id
LEFT JOIN employee e ON i1.employee_id = e.id
WHERE $where_clause i1.qty > 0 AND
i1.trans_id IN ( SELECT i.trans_id FROM inventory i GROUP BY i.trans_id HAVING COUNT(i.trans_id) = 1 )
GROUP BY $group_clause
3c938e03 Martin Helmling
ORDER BY r_${sort_spec}) AS lines WHERE r_qty>0|;

6aaed579 Martin Helmling
my @all_vars = (@filter_vars,@filter_vars,@filter_vars);
83914eeb Moritz Bunkus
6aaed579 Martin Helmling
if ($filter{limit}) {
$query .= " LIMIT ?";
push @all_vars,$filter{limit};
}
if ($filter{offset}) {
$query .= " OFFSET ?";
push @all_vars, $filter{offset};
}

my $sth = prepare_execute_query($form, $dbh, $query, @all_vars);
83914eeb Moritz Bunkus
17409513 Moritz Bunkus
my ($h_oe_id, $q_oe_id);
if ($form->{l_oe_id}) {
$q_oe_id = <<SQL;
SELECT dord.id AS id, dord.donumber AS number,
CASE
WHEN dord.customer_id IS NULL THEN 'purchase_delivery_order'
ELSE 'sales_delivery_order'
END AS type
FROM delivery_orders dord
WHERE dord.id = ?

UNION

6d808fff Bernd Bleßmann
SELECT ar.id AS id, ar.invnumber AS number, 'sales_invoice' AS type
FROM ar
WHERE ar.id = (SELECT trans_id FROM invoice WHERE id = ?)

UNION

SELECT ap.id AS id, ap.invnumber AS number, 'purchase_invoice' AS type
FROM ap
WHERE ap.id = (SELECT trans_id FROM invoice WHERE id = ?)
17409513 Moritz Bunkus
SQL
$h_oe_id = prepare_query($form, $dbh, $q_oe_id);
}

83914eeb Moritz Bunkus
my @contents = ();
c510d88b Sven Schöling
while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
83914eeb Moritz Bunkus
map { /^r_/; $ref->{"$'"} = $ref->{$_} } keys %$ref;
my $qty = $ref->{"qty"} * 1;

next unless ($qty > 0);

if ($f_qty_op) {
my $part_unit = $all_units->{$ref->{"partunit"}};
next unless ($part_unit && ($part_unit->{"base_unit"} eq $f_qty_base_unit));
$qty *= $part_unit->{"factor"};
next if (('=' eq $f_qty_op) && ($qty != $f_qty));
next if (('>=' eq $f_qty_op) && ($qty < $f_qty));
next if (('<=' eq $f_qty_op) && ($qty > $f_qty));
}

6d808fff Bernd Bleßmann
if ($h_oe_id && ($ref->{oe_id} || $ref->{invoice_id})) {
0b5e2b1b Bernd Bleßmann
do_statement($form, $h_oe_id, $q_oe_id, $ref->{oe_id}, ($ref->{invoice_id}) x 2);
17409513 Moritz Bunkus
$ref->{oe_id_info} = $h_oe_id->fetchrow_hashref() || {};
}

83914eeb Moritz Bunkus
push @contents, $ref;
}

$sth->finish();
17409513 Moritz Bunkus
$h_oe_id->finish() if $h_oe_id;
83914eeb Moritz Bunkus
$main::lxdebug->leave_sub();

return @contents;
}

#
# This sub is the primary function to retrieve information about items in warehouses.
# $filter is a hashref and supports the following keys:
# - warehouse_id - will return matches with this warehouse_id only
# - partnumber - will return only matches where the given string is a substring of the partnumber
# - partsid - will return matches with this parts_id only
65d2537d Martin Helmling
# - classification_id - will return matches with this parts with this classification only
83914eeb Moritz Bunkus
# - description - will return only matches where the given string is a substring of the description
# - chargenumber - will return only matches where the given string is a substring of the chargenumber
096f9e3e Bernd Bleßmann
# - bestbefore - will return only matches with this bestbefore date
c09536f4 Sven Schöling
# - ean - will return only matches where the given string is a substring of the ean as stored in the table parts (article)
83914eeb Moritz Bunkus
# - charge_ids - must be an arrayref. will return contents with these ids only
# - expires_in - will only return matches that expire within the given number of days
# will also add a column named 'has_expired' containing if the match has already expired or not
# - hazardous - will return matches with the flag hazardous only
# - oil - will return matches with the flag oil only
# - qty, qty_op - quantity filter (more info to come)
# - sort, order_by - sorting (more to come)
# - reservation - will provide an extra column containing the amount reserved of this match
# note: reservation flag turns off warehouse_* or bin_* information. both together don't make sense, since reserved info is stored separately
#
sub get_warehouse_report {
$main::lxdebug->enter_sub();

my $self = shift;
my %filter = @_;

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $all_units = AM->retrieve_units($myconfig, $form);

# connect to database
my $dbh = $form->get_standard_dbh($myconfig);

# filters
9a0d550b Moritz Bunkus
my (@filter_ary, @filter_vars, @wh_bin_filter_ary, @wh_bin_filter_vars);
83914eeb Moritz Bunkus
delete $form->{include_empty_bins} unless ($form->{l_warehousedescription} || $form->{l_bindescription});

if ($filter{warehouse_id}) {
push @wh_bin_filter_ary, "w.id = ?";
push @wh_bin_filter_vars, $filter{warehouse_id};
}

if ($filter{bin_id}) {
push @wh_bin_filter_ary, "b.id = ?";
push @wh_bin_filter_vars, $filter{bin_id};
}

push @filter_ary, @wh_bin_filter_ary;
push @filter_vars, @wh_bin_filter_vars;

if ($filter{partnumber}) {
push @filter_ary, "p.partnumber ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{partnumber});
83914eeb Moritz Bunkus
}

65d2537d Martin Helmling
if ($filter{classification_id}) {
push @filter_ary, "p.classification_id = ?";
push @filter_vars, $filter{classification_id};
}

83914eeb Moritz Bunkus
if ($filter{description}) {
push @filter_ary, "p.description ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{description});
83914eeb Moritz Bunkus
}

if ($filter{partsid}) {
push @filter_ary, "p.id = ?";
push @filter_vars, $filter{partsid};
}

if ($filter{chargenumber}) {
push @filter_ary, "i.chargenumber ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{chargenumber});
83914eeb Moritz Bunkus
}
096f9e3e Bernd Bleßmann
d16c1b3c Moritz Bunkus
if (trim($form->{bestbefore})) {
096f9e3e Bernd Bleßmann
push @filter_ary, "?::DATE = i.bestbefore::DATE";
d16c1b3c Moritz Bunkus
push @filter_vars, trim($form->{bestbefore});
096f9e3e Bernd Bleßmann
}

3c938e03 Martin Helmling
if ($filter{classification_id}) {
push @filter_ary, "p.classification_id = ?";
push @filter_vars, $filter{classification_id};
}

21ca8cb7 Geoffrey Richardson
if ($filter{ean}) {
push @filter_ary, "p.ean ILIKE ?";
bed19453 Moritz Bunkus
push @filter_vars, like($filter{ean});
21ca8cb7 Geoffrey Richardson
}
83914eeb Moritz Bunkus
d16c1b3c Moritz Bunkus
if (trim($filter{date})) {
8fd88684 Geoffrey Richardson
push @filter_ary, "i.shippingdate <= ?";
d16c1b3c Moritz Bunkus
push @filter_vars, trim($filter{date});
45620ee5 Sven Schöling
}
60d62c9e Jan Büren
if (!$filter{include_invalid_warehouses}){
push @filter_ary, "NOT (w.invalid)";
}
45620ee5 Sven Schöling
83914eeb Moritz Bunkus
# prepare qty comparison for later filtering
my ($f_qty_op, $f_qty, $f_qty_base_unit);

if ($filter{qty_op} && defined $filter{qty} && $filter{qty_unit} && $all_units->{$filter{qty_unit}}) {
$f_qty_op = $filter{qty_op};
$f_qty = $filter{qty} * $all_units->{$filter{qty_unit}}->{factor};
$f_qty_base_unit = $all_units->{$filter{qty_unit}}->{base_unit};
}

map { $_ = "(${_})"; } @filter_ary;

# if of a property number or description is requested,
# automatically check the matching id too.
map { $form->{"l_${_}id"} = "Y" if ($form->{"l_${_}description"} || $form->{"l_${_}number"}); } qw(warehouse bin);

# make order, search in $filter and $form
19688fca Moritz Bunkus
my $sort_col = $form->{sort};
my $sort_order = $form->{order};

$sort_col = $filter{sort} unless $sort_col;
f71b2873 Jan Büren
# falls $sort_col gar nicht in dem Bericht aufgenommen werden soll,
# führt ein entsprechenes order by $sort_col zu einem SQL-Fehler
# entsprechend parts_id als default lassen, wenn $sort_col UND l_$sort_col
# vorhanden sind (bpsw. l_partnumber = 'Y', für in Bericht aufnehmen).
# S.a. Bug 1597 jb 12.5.2011
$sort_col = "parts_id" unless ($sort_col && $form->{"l_$sort_col"});
19688fca Moritz Bunkus
$sort_order = $filter{order} unless $sort_order;
$sort_col =~ s/ASC|DESC//; # kill stuff left in from previous queries
my $orderby = $sort_col;
my $sort_spec = "${sort_col} " . ($sort_order ? " DESC" : " ASC");
83914eeb Moritz Bunkus
my $where_clause = join " AND ", ("1=1", @filter_ary);

my %select_tokens = (
"parts_id" => "i.parts_id",
"qty" => "SUM(i.qty)",
"warehouseid" => "i.warehouse_id",
"partnumber" => "p.partnumber",
"partdescription" => "p.description",
65d2537d Martin Helmling
"classification_id" => "p.classification_id",
"part_type" => "p.part_type",
83914eeb Moritz Bunkus
"bindescription" => "b.description",
"binid" => "b.id",
"chargenumber" => "i.chargenumber",
096f9e3e Bernd Bleßmann
"bestbefore" => "i.bestbefore",
c09536f4 Sven Schöling
"ean" => "p.ean",
83914eeb Moritz Bunkus
"chargeid" => "c.id",
"warehousedescription" => "w.description",
"partunit" => "p.unit",
82498fb7 Moritz Bunkus
"stock_value" => "p.lastcost / COALESCE(pfac.factor, 1)",
3c938e03 Martin Helmling
"purchase_price" => "p.lastcost",
83914eeb Moritz Bunkus
);
65d2537d Martin Helmling
$form->{l_classification_id} = 'Y';
$form->{l_part_type} = 'Y';

4895d0c6 Moritz Bunkus
my $select_clause = join ', ', map { +/^l_/; "$select_tokens{$'} AS $'" }
( grep( { !/qty/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form),
83914eeb Moritz Bunkus
qw(l_parts_id l_qty l_partunit) );

my $group_clause = join ", ", map { +/^l_/; "$'" }
4895d0c6 Moritz Bunkus
( grep( { !/qty/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form),
83914eeb Moritz Bunkus
qw(l_parts_id l_partunit) );

82498fb7 Moritz Bunkus
my %join_tokens = (
"stock_value" => "LEFT JOIN price_factors pfac ON (p.price_factor_id = pfac.id)",
);

my $joins = join ' ', grep { $_ } map { +/^l_/; $join_tokens{"$'"} }
( grep( { !/qty/ and /^l_/ and $form->{$_} eq 'Y' } keys %$form),
qw(l_parts_id l_qty l_partunit) );

83914eeb Moritz Bunkus
my $query =
3c938e03 Martin Helmling
qq|SELECT * FROM ( SELECT $select_clause
83914eeb Moritz Bunkus
FROM inventory i
LEFT JOIN parts p ON i.parts_id = p.id
LEFT JOIN bin b ON i.bin_id = b.id
LEFT JOIN warehouse w ON i.warehouse_id = w.id
82498fb7 Moritz Bunkus
$joins
83914eeb Moritz Bunkus
WHERE $where_clause
9a0d550b Moritz Bunkus
GROUP BY $group_clause
3c938e03 Martin Helmling
ORDER BY $sort_spec ) AS lines WHERE qty<>0|;

6aaed579 Martin Helmling
if ($filter{limit}) {
$query .= " LIMIT ?";
push @filter_vars,$filter{limit};
}
if ($filter{offset}) {
$query .= " OFFSET ?";
push @filter_vars, $filter{offset};
}
my $sth = prepare_execute_query($form, $dbh, $query, @filter_vars );
83914eeb Moritz Bunkus
my (%non_empty_bins, @all_fields, @contents);

c510d88b Sven Schöling
while (my $ref = $sth->fetchrow_hashref("NAME_lc")) {
83914eeb Moritz Bunkus
$ref->{qty} *= 1;
my $qty = $ref->{qty};

d7b7f763 Moritz Bunkus
next unless ($qty != 0);
83914eeb Moritz Bunkus
if ($f_qty_op) {
my $part_unit = $all_units->{$ref->{partunit}};
next if (!$part_unit || ($part_unit->{base_unit} ne $f_qty_base_unit));
$qty *= $part_unit->{factor};
next if (('=' eq $f_qty_op) && ($qty != $f_qty));
next if (('>=' eq $f_qty_op) && ($qty < $f_qty));
next if (('<=' eq $f_qty_op) && ($qty > $f_qty));
}

if ($form->{include_empty_bins}) {
$non_empty_bins{$ref->{binid}} = 1;
@all_fields = keys %{ $ref } unless (@all_fields);
}

9de83d1a Moritz Bunkus
$ref->{stock_value} = ($ref->{stock_value} || 0) * $ref->{qty};
82498fb7 Moritz Bunkus
83914eeb Moritz Bunkus
push @contents, $ref;
}

$sth->finish();

if ($form->{include_empty_bins}) {
$query =
qq|SELECT
w.id AS warehouseid, w.description AS warehousedescription,
b.id AS binid, b.description AS bindescription
FROM bin b
LEFT JOIN warehouse w ON (b.warehouse_id = w.id)|;

@filter_ary = @wh_bin_filter_ary;
@filter_vars = @wh_bin_filter_vars;

my @non_empty_bin_ids = keys %non_empty_bins;
if (@non_empty_bin_ids) {
push @filter_ary, qq|NOT b.id IN (| . join(', ', map { '?' } @non_empty_bin_ids) . qq|)|;
push @filter_vars, @non_empty_bin_ids;
}

$query .= qq| WHERE | . join(' AND ', map { "($_)" } @filter_ary) if (@filter_ary);

$sth = prepare_execute_query($form, $dbh, $query, @filter_vars);

4f63ea87 Geoffrey Richardson
while (my $ref = $sth->fetchrow_hashref()) {
83914eeb Moritz Bunkus
map { $ref->{$_} ||= "" } @all_fields;
push @contents, $ref;
}
$sth->finish();

if (grep { $orderby eq $_ } qw(bindescription warehousedescription)) {
@contents = sort { ($a->{$orderby} cmp $b->{$orderby}) * (($form->{order}) ? 1 : -1) } @contents;
}
}

$main::lxdebug->leave_sub();

return @contents;
}

sub convert_qty_op {
$main::lxdebug->enter_sub();

my ($self, $qty_op) = @_;

if (!$qty_op || ($qty_op eq "dontcare")) {
$main::lxdebug->leave_sub();
return undef;
}

if ($qty_op eq "atleast") {
$qty_op = '>=';
} elsif ($qty_op eq "atmost") {
$qty_op = '<=';
} else {
$qty_op = '=';
}

$main::lxdebug->leave_sub();

return $qty_op;
}

sub retrieve_transfer_types {
$main::lxdebug->enter_sub();

my $self = shift;
my $direction = shift;

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $form->get_standard_dbh($myconfig);

my $types = selectall_hashref_query($form, $dbh, qq|SELECT * FROM transfer_type WHERE direction = ? ORDER BY sortkey|, $direction);

$main::lxdebug->leave_sub();

return $types;
}

sub get_basic_bin_info {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;

Common::check_params(\%params, qw(id));

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $params{dbh} || $form->get_standard_dbh();

my @ids = 'ARRAY' eq ref $params{id} ? @{ $params{id} } : ($params{id});

my $query =
qq|SELECT b.id AS bin_id, b.description AS bin_description,
w.id AS warehouse_id, w.description AS warehouse_description
FROM bin b
LEFT JOIN warehouse w ON (b.warehouse_id = w.id)
WHERE b.id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;

my $result = selectall_hashref_query($form, $dbh, $query, map { conv_i($_) } @ids);

if ('' eq ref $params{id}) {
$result = $result->[0] || { };
$main::lxdebug->leave_sub();

return $result;
}

$main::lxdebug->leave_sub();

return map { $_->{bin_id} => $_ } @{ $result };
}
8814ec73 Martin Helmling
sub get_basic_warehouse_info {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;

Common::check_params(\%params, qw(id));

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $params{dbh} || $form->get_standard_dbh();

my @ids = 'ARRAY' eq ref $params{id} ? @{ $params{id} } : ($params{id});

my $query =
qq|SELECT w.id AS warehouse_id, w.description AS warehouse_description
FROM warehouse w
WHERE w.id IN (| . join(', ', ('?') x scalar(@ids)) . qq|)|;

my $result = selectall_hashref_query($form, $dbh, $query, map { conv_i($_) } @ids);

if ('' eq ref $params{id}) {
$result = $result->[0] || { };
$main::lxdebug->leave_sub();

return $result;
}

$main::lxdebug->leave_sub();

return map { $_->{warehouse_id} => $_ } @{ $result };
}
4f63ea87 Geoffrey Richardson
#
c09536f4 Sven Schöling
# Eingabe: Teilenummer, Lagernummer (warehouse)
# Ausgabe: Die maximale Anzahl der Teile in diesem Lager
4f63ea87 Geoffrey Richardson
#
sub get_max_qty_parts {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;

17409513 Moritz Bunkus
Common::check_params(\%params, qw(parts_id warehouse_id)); #die brauchen wir
4f63ea87 Geoffrey Richardson
my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $params{dbh} || $form->get_standard_dbh();

096f9e3e Bernd Bleßmann
my $query = qq| SELECT SUM(qty), bin_id, chargenumber, bestbefore FROM inventory where parts_id = ? AND warehouse_id = ? GROUP BY bin_id, chargenumber, bestbefore|;
4f63ea87 Geoffrey Richardson
my $sth_QTY = prepare_execute_query($form, $dbh, $query, ,$params{parts_id}, $params{warehouse_id}); #info: aufruf an DBUtils.pm
17409513 Moritz Bunkus
0f214df5 Waldemar Toews
4f63ea87 Geoffrey Richardson
my $max_qty_parts = 0; #Initialisierung mit 0
096f9e3e Bernd Bleßmann
while (my $ref = $sth_QTY->fetchrow_hashref()) { # wir laufen über alle Haltbarkeiten, chargen und Lagerorte (s.a. SQL-Query oben)
17409513 Moritz Bunkus
$max_qty_parts += $ref->{sum};
4f63ea87 Geoffrey Richardson
}

$main::lxdebug->leave_sub();

return $max_qty_parts;
}

#
c09536f4 Sven Schöling
# Eingabe: Teilenummer, Lagernummer (warehouse)
# Ausgabe: Die Beschreibung der Ware bzw. Erzeugnis
4f63ea87 Geoffrey Richardson
#
sub get_part_description {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;

2e50c674 Jan Büren
Common::check_params(\%params, qw(parts_id)); #die brauchen wir
4f63ea87 Geoffrey Richardson
my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $params{dbh} || $form->get_standard_dbh();

my $query = qq| SELECT partnumber, description FROM parts where id = ? |;

my $sth = prepare_execute_query($form, $dbh, $query, ,$params{parts_id}); #info: aufruf zu DBUtils.pm
17409513 Moritz Bunkus
my $ref = $sth->fetchrow_hashref();
my $part_description = $ref->{partnumber} . " " . $ref->{description};
4f63ea87 Geoffrey Richardson
$main::lxdebug->leave_sub();

return $part_description;
}
52d18c01 Jan Büren
#
# Eingabe: Teilenummer, Lagerplatz_Id (bin_id)
# Ausgabe: Die maximale Anzahl der Teile in diesem Lagerplatz
# Bzw. Fehler, falls Chargen oder bestbefore
# bei eingelagerten Teilen definiert sind.
#
sub get_max_qty_parts_bin {
$main::lxdebug->enter_sub();

my $self = shift;
my %params = @_;

Common::check_params(\%params, qw(parts_id bin_id)); #die brauchen wir

my $myconfig = \%main::myconfig;
my $form = $main::form;

my $dbh = $params{dbh} || $form->get_standard_dbh();

my $query = qq| SELECT SUM(qty), chargenumber, bestbefore FROM inventory where parts_id = ?
AND bin_id = ? GROUP BY chargenumber, bestbefore|;

my $sth_QTY = prepare_execute_query($form, $dbh, $query, ,$params{parts_id}, $params{bin_id}); #info: aufruf an DBUtils.pm
83914eeb Moritz Bunkus
52d18c01 Jan Büren
my $max_qty_parts = 0; #Initialisierung mit 0
# falls derselbe artikel mehrmals eingelagert ist
# chargennummer, muss entsprechend händisch agiert werden
my $i = 0;
my $error;
while (my $ref = $sth_QTY->fetchrow_hashref()) { # wir laufen über alle Haltbarkeiten und Chargen(s.a. SQL-Query oben)
$max_qty_parts += $ref->{sum};
$i++;
336f48b8 Daniel Popiuk
if (($ref->{chargenumber} || $ref->{bestbefore}) && $ref->{sum} != 0){
e2cb496b Jan Büren
$error = 1;
52d18c01 Jan Büren
}
}
$main::lxdebug->leave_sub();

return ($max_qty_parts, $error);
}
83914eeb Moritz Bunkus
c7cabbb2 Sven Schöling
1;

__END__

=head1 NAME

SL::WH - Warehouse backend

=head1 SYNOPSIS

use SL::WH;
WH->transfer(\%params);

=head1 DESCRIPTION

ae00d68a Geoffrey Richardson
Backend for kivitendo warehousing functions.
c7cabbb2 Sven Schöling
=head1 FUNCTIONS

=head2 transfer \%PARAMS, [ \%PARAMS, ... ]

This is the main function to manipulate warehouse contents. A typical transfer
is called like this:

WH->transfer->({
parts_id => 6342,
qty => 12.45,
transfer_type => 'transfer',
src_warehouse_id => 12,
ae00d68a Geoffrey Richardson
src_bin_id => 23,
c7cabbb2 Sven Schöling
dst_warehouse_id => 25,
dst_bin_id => 167,
});

It will generate an entry in inventory representing the transfer. Note that
parts_id, qty, and transfer_type are mandatory. Depending on the transfer_type
a destination or a src is mandatory.

transfer accepts more than one transaction parameter, each being a hash ref. If
more than one is supplied, it is guaranteed, that all are processed in the same
transaction.

1bbcb32c Bernd Bleßmann
It is possible to record stocktakings within this transaction as well.
This is useful if the transfer is the result of stocktaking (see also
C<SL::Controller::Inventory>). To do so the parameters C<record_stocktaking>,
C<stocktaking_qty> and C<stocktaking_cutoff_date> hava to be given.
If stocktaking should be saved, then the transfer quantity can be zero. In this
case no entry in inventory will be made, but only the stocktaking entry.

c7cabbb2 Sven Schöling
Here is a full list of parameters. All "_id" parameters except oe and
orderitems can be called without id with RDB objects as well.

=over 4

=item parts_id

The id of the article transferred. Does not check if the article is a service.
Mandatory.

=item qty

Quantity of the transaction. Mandatory.

=item unit

Unit of the transaction. Optional.

=item transfer_type

=item transfer_type_id

The type of transaction. The first version is a string describing the
transaction (the types 'transfer' 'in' 'out' and a few others are present on
every system), the id is the hard id of a transfer_type from the database.

Depending of the direction of the transfer_type, source and/or destination must
be specified.

One of transfer_type or transfer_type_id is mandatory.

=item src_warehouse_id

=item src_bin_id

Warehouse and bin from which to transfer. Mandatory in transfer and out
directions. Ignored in in directions.

=item dst_warehouse_id

=item dst_bin_id

Warehouse and bin to which to transfer. Mandatory in transfer and in
directions. Ignored in out directions.

=item chargenumber

If given, the transfer will transfer only articles with this chargenumber.
Optional.

=item orderitem_id

Reference to an orderitem for which this transfer happened. Optional

=item oe_id

Reference to an order for which this transfer happened. Optional

=item comment

An optional comment.

=item best_before

An expiration date. Note that this is not by default used by C<warehouse_report>.

1bbcb32c Bernd Bleßmann
=item record_stocktaking

A boolean flag to indicate that a stocktaking entry should be saved.

=item stocktaking_qty

The quantity for the stocktaking entry.

=item stocktaking_cutoff_date

The cutoff date for the stocktaking entry.

c7cabbb2 Sven Schöling
=back

6c984131 Jan Büren
=head2 create_assembly \%PARAMS, [ \%PARAMS, ... ]

Creates an assembly if all defined items are available.

233f338c Jan Büren
Assembly item(s) will be stocked out and the assembly will be stocked in,
taking into account the qty and units which can be defined for each
aa0c7e72 Sven Schöling
assembly item separately.
233f338c Jan Büren
6c984131 Jan Büren
The calling params originate from C<transfer> but only parts_id with the
attribute assembly are processed.

The typical params would be:

my %TRANSFER = (
'login' => $::myconfig{login},
'dst_warehouse_id' => $form->{warehouse_id},
'dst_bin_id' => $form->{bin_id},
'chargenumber' => $form->{chargenumber},
'bestbefore' => $form->{bestbefore},
'assembly_id' => $form->{parts_id},
'qty' => $form->{qty},
'comment' => $form->{comment}
);

=head3 Prerequisites

All of these prerequisites have to be trueish, otherwise the function will exit
unsuccessfully with a return value of undef.

=over 4

=item Mandantory params

assembly_id, qty, login, dst_warehouse_id and dst_bin_id are mandatory.

=item Subset named 'Assembly' of data set 'Part'

assembly_id has to be an id in the table parts with the valid subset assembly.

233f338c Jan Büren
=item Assembly is composed of assembly item(s)
6c984131 Jan Büren
233f338c Jan Büren
There has to be at least one data set in the table assembly referenced to this assembly_id.
6c984131 Jan Büren
233f338c Jan Büren
=item Assembly cannot be destroyed or disassembled
6c984131 Jan Büren
233f338c Jan Büren
Assemblies are like cakes. You cannot disassemble it. NEVER.
No negative nor zero qty's are valid inputs.
6c984131 Jan Büren
=item The assembly item(s) have to be in the same warehouse

inventory.warehouse_id equals dst_warehouse_id (client configurable).

=item The assembly item(s) have to be in stock with the qty needed

233f338c Jan Büren
I can only make a cake by receipt if I have ALL ingredients and
in the needed stock amount.
The qty of stocked in assembly item(s) has to fit into the
number of the qty of the assemblies, which are going to be created (client configurable).
6c984131 Jan Büren
=item assembly item(s) with the parts set 'service' are ignored

The subset 'Services' of part will not transferred for assembly item(s).

=back

Client configurable prerequisites can be changed with different
prerequisites as described in client_config (s.a. next chapter).


=head2 default creation of assembly

233f338c Jan Büren
The valid state of the assembly item(s) used for the assembly process are
'out' for the general direction and 'used' as the specific reason.
The valid state of the assembly is 'in' for the direction and 'assembled'
as the specific reason.
6c984131 Jan Büren
The method is transaction safe, in case of errors not a single entry will be made
in inventory.

Two prerequisites can be changed with this global parameters

=over 2

=item $::instance_conf->get_transfer_default_warehouse_for_assembly

If trueish we try to get all the items form the default bins defined in parts
and do not try to find them in the destination warehouse. Returns an
error if not all items have set a default bin in parts.

=item $::instance_conf->get_bin_id_ignore_onhand

If trueish we can create assemblies even if we do not have enough items in stock.
The needed qty will be booked in a special bin, which has to be configured in
the client config.

=back




c7cabbb2 Sven Schöling
=head1 BUGS

07b14d1f Sven Schöling
None yet.

c7cabbb2 Sven Schöling
=head1 AUTHOR

=cut

83914eeb Moritz Bunkus
1;