+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 };
+}
+#
+# Eingabe: Teilenummer, Lagernummer (warehouse)
+# Ausgabe: Die maximale Anzahl der Teile in diesem Lager
+#
+sub get_max_qty_parts {
+$main::lxdebug->enter_sub();
+
+ my $self = shift;
+ my %params = @_;
+
+ Common::check_params(\%params, qw(parts_id warehouse_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), bin_id, chargenumber, bestbefore FROM inventory where parts_id = ? AND warehouse_id = ? GROUP BY bin_id, chargenumber, bestbefore|;
+ my $sth_QTY = prepare_execute_query($form, $dbh, $query, ,$params{parts_id}, $params{warehouse_id}); #info: aufruf an DBUtils.pm
+
+
+ my $max_qty_parts = 0; #Initialisierung mit 0
+ while (my $ref = $sth_QTY->fetchrow_hashref()) { # wir laufen über alle Haltbarkeiten, chargen und Lagerorte (s.a. SQL-Query oben)
+ $max_qty_parts += $ref->{sum};
+ }
+
+ $main::lxdebug->leave_sub();
+
+ return $max_qty_parts;
+}
+
+#
+# Eingabe: Teilenummer, Lagernummer (warehouse)
+# Ausgabe: Die Beschreibung der Ware bzw. Erzeugnis
+#
+sub get_part_description {
+$main::lxdebug->enter_sub();
+
+ my $self = shift;
+ my %params = @_;
+
+ Common::check_params(\%params, qw(parts_id)); #die brauchen wir
+
+ 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
+
+ my $ref = $sth->fetchrow_hashref();
+ my $part_description = $ref->{partnumber} . " " . $ref->{description};
+
+ $main::lxdebug->leave_sub();
+
+ return $part_description;
+}
+#
+# 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
+
+ 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++;
+ if (($ref->{chargenumber} || $ref->{bestbefore}) && $ref->{sum} != 0){
+ $error = 1;
+ }
+ }
+ $main::lxdebug->leave_sub();
+
+ return ($max_qty_parts, $error);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+SL::WH - Warehouse backend
+
+=head1 SYNOPSIS
+
+ use SL::WH;
+ WH->transfer(\%params);
+
+=head1 DESCRIPTION
+
+Backend for kivitendo warehousing functions.
+
+=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,
+ src_bin_id => 23,
+ 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.
+
+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>.
+
+=back
+
+=head2 create_assembly \%PARAMS, [ \%PARAMS, ... ]
+
+Creates an assembly if all defined items are available.
+
+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
+assembly item separately.
+
+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.
+
+=item Assembly is composed of assembly item(s)
+
+ There has to be at least one data set in the table assembly referenced to this assembly_id.
+
+=item Assembly cannot be destroyed or disassembled
+
+ Assemblies are like cakes. You cannot disassemble it. NEVER.
+ No negative nor zero qty's are valid inputs.
+
+=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
+
+ 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).
+
+=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
+
+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.
+
+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
+
+
+
+
+=head1 BUGS
+
+None yet.
+
+=head1 AUTHOR
+
+=cut