+sub transfer_stock {
+ my (%params) = @_;
+
+ # check params:
+ die "missing params" unless ( $params{parts_id} or $params{part} ) and $params{from_bin} and $params{to_bin};
+
+ my $part;
+ if ( $params{parts_id} ) {
+ $part = SL::DB::Manager::Part->find_by( id => delete $params{parts_id} ) or die "illegal parts_id";
+ } else {
+ $part = delete $params{part};
+ }
+ die "illegal part" unless ref($part) eq 'SL::DB::Part';
+
+ my $from_bin = delete $params{from_bin};
+ my $to_bin = delete $params{to_bin};
+ die "illegal bins" unless ref($from_bin) eq 'SL::DB::Bin' and ref($to_bin) eq 'SL::DB::Bin';
+
+ my $qty = delete($params{qty});
+ die "qty must be > 0" unless $qty > 0;
+
+ # set defaults
+ my $transfer_type = SL::DB::Manager::TransferType->find_by(description => 'transfer') or die "can't determine transfer type";
+ my $employee_id = delete $params{employee_id} // SL::DB::Manager::Employee->current->id;
+
+ my $WH_params = {
+ 'bestbefore' => undef,
+ 'change_default_bin' => undef,
+ 'chargenumber' => '',
+ 'comment' => delete $params{comment} // '',
+ 'dst_bin_id' => $to_bin->id,
+ 'dst_warehouse_id' => $to_bin->warehouse_id,
+ 'parts_id' => $part->id,
+ 'qty' => $qty,
+ 'src_bin_id' => $from_bin->id,
+ 'src_warehouse_id' => $from_bin->warehouse_id,
+ 'transfer_type_id' => $transfer_type->id,
+ };
+
+ WH->transfer($WH_params);
+
+ return 1;
+
+ # do it manually via rose:
+ # my $trans_id;
+
+ # my $db = SL::DB::Inventory->new->db;
+ # $db->with_transaction(sub{
+ # ($trans_id) = $db->dbh->selectrow_array("select nextval('id')", {});
+ # die "no trans_id" unless $trans_id;
+
+ # my %params = (
+ # shippingdate => delete $params{shippingdate} // DateTime->today,
+ # employee_id => $employee_id,
+ # trans_id => $trans_id,
+ # trans_type_id => $transfer_type->id,
+ # parts_id => $part->id,
+ # comment => delete $params{comment} || 'Umlagerung',
+ # );
+
+ # SL::DB::Inventory->new(
+ # warehouse_id => $from_bin->warehouse_id,
+ # bin_id => $from_bin->id,
+ # qty => $qty * -1,
+ # %params,
+ # )->save;
+
+ # SL::DB::Inventory->new(
+ # warehouse_id => $to_bin->warehouse_id,
+ # bin_id => $to_bin->id,
+ # qty => $qty,
+ # %params,
+ # )->save;
+ # }) or die $@ . "\n";
+ # return 1;
+}
+
+sub _transfer {
+ my (%params) = @_;
+
+ my $transfer_type = delete $params{transfer_type};
+
+ die "param transfer_type is not a SL::DB::TransferType object: " . Dumper($transfer_type)
+ unless ref($transfer_type) eq 'SL::DB::TransferType';
+
+ my $shippingdate = delete $params{shippingdate} // DateTime->today;
+
+ my $part = delete($params{part}) or croak 'part missing';
+ my $qty = delete($params{qty}) or croak 'qty missing';
+
+ # distinguish absolute qty in inventory depending on transfer type direction
+ $qty *= -1 if $transfer_type->direction eq 'out';
+
+ # use defaults for unit/wh/bin is they exist and nothing else is specified
+ my $unit = delete($params{unit}) // $part->unit or croak 'unit missing';
+ my $bin = delete($params{bin}) // $part->bin or croak 'bin missing';
+ # if bin is given, we don't need a warehouse param
+ my $wh = $bin->warehouse or croak 'wh missing';
+
+ WH->transfer({
+ parts_id => $part->id,
+ dst_bin => $bin,
+ dst_wh => $wh,
+ qty => $qty,
+ transfer_type => $transfer_type,
+ unit => $unit,
+ comment => delete $params{comment},
+ shippingdate => $shippingdate,
+ });
+}
+
+sub transfer_in {
+ my (%params) = @_;
+
+ my $transfer_type = delete $params{transfer_type} // 'stock';
+
+ my $transfer_type_obj = SL::DB::Manager::TransferType->find_by(
+ direction => 'in',
+ description => $transfer_type,
+ ) or die "Can't find transfer_type with direction in and description " . $params{transfer_type};
+
+ $params{transfer_type} = $transfer_type_obj;
+
+ _transfer(%params);
+}
+
+sub transfer_out {
+ my (%params) = @_;
+
+ my $transfer_type = delete $params{transfer_type} // 'shipped';
+
+ my $transfer_type_obj = SL::DB::Manager::TransferType->find_by(
+ direction => 'out',
+ description => $transfer_type,
+ ) or die "Can't find transfer_type with direction in and description " . $params{transfer_type};
+
+ $params{transfer_type} = $transfer_type_obj;
+
+ _transfer(%params);
+}
+
+sub transfer_sales_delivery_order {
+ my ($sales_delivery_order) = @_;
+ die "first argument must be a sales delivery order Rose DB object"
+ unless ref($sales_delivery_order) eq 'SL::DB::DeliveryOrder'
+ and $sales_delivery_order->is_sales;
+
+ die "the delivery order has already been delivered" if $sales_delivery_order->delivered;
+
+ my ($wh, $bin, $trans_type);
+
+ $sales_delivery_order->db->with_transaction(sub {
+
+ foreach my $doi ( @{ $sales_delivery_order->items } ) {
+ next if $doi->part->is_service or $doi->part->is_assortment;
+ my $trans_type = SL::DB::Manager::TransferType->find_by(direction => 'out', description => 'shipped');
+ transfer_delivery_order_item($doi, $wh, $bin, $trans_type);
+ };
+ $sales_delivery_order->delivered(1);
+ $sales_delivery_order->save(changes_only=>1);
+ 1;
+ }) or die "error while transferring sales_delivery_order: " . $sales_delivery_order->db->error;
+};
+
+sub transfer_purchase_delivery_order {
+ my ($purchase_delivery_order) = @_;
+ die "first argument must be a purchase delivery order Rose DB object"
+ unless ref($purchase_delivery_order) eq 'SL::DB::DeliveryOrder'
+ and not $purchase_delivery_order->is_sales;
+
+ my ($wh, $bin, $trans_type);
+
+ $purchase_delivery_order->db->with_transaction(sub {
+
+ foreach my $doi ( @{ $purchase_delivery_order->items } ) {
+ my $trans_type = SL::DB::Manager::TransferType->find_by(direction => 'in', description => 'stock');
+ transfer_delivery_order_item($doi, $wh, $bin, $trans_type);
+ };
+ 1;
+ }) or die "error while transferring purchase_Delivery_order: " . $purchase_delivery_order->db->error;
+};
+
+sub transfer_delivery_order_item {
+ my ($doi, $wh, $bin, $trans_type) = @_;
+
+ unless ( defined $trans_type and ref($trans_type eq 'SL::DB::TransferType') ) {
+ if ( $doi->record->is_sales ) {
+ $trans_type //= SL::DB::Manager::TransferType->find_by(direction => 'out', description => 'shipped');
+ } else {
+ $trans_type //= SL::DB::Manager::TransferType->find_by(direction => 'in', description => 'stock');
+ }
+ }
+
+ $bin //= $doi->part->bin;
+ $wh //= $doi->part->warehouse;
+
+ die "no bin and wh specified and part has no default bin or wh" unless $bin and $wh;
+
+ my $employee = SL::DB::Manager::Employee->current || die "No employee";
+
+ # dois are converted to base_qty, which is qty
+ # AM->convert_unit( 'g' => 'kg') * 1000; # 1
+ # $doi->unit $doi->part->unit $doi->qty
+ my $dois = SL::DB::DeliveryOrderItemsStock->new(
+ delivery_order_item => $doi,
+ qty => AM->convert_unit($doi->unit => $doi->part->unit) * $doi->qty,
+ unit => $doi->part->unit,
+ warehouse_id => $wh->id,
+ bin_id => $bin->id,
+ )->save;
+
+ my $inventory = SL::DB::Inventory->new(
+ parts => $dois->delivery_order_item->part,
+ qty => $dois->delivery_order_item->record->is_sales ? $dois->qty * -1 : $dois->qty,
+ oe => $doi->record,
+ warehouse_id => $dois->warehouse_id,
+ bin_id => $dois->bin_id,
+ trans_type_id => $trans_type->id,
+ delivery_order_items_stock => $dois,
+ trans_id => $dois->id,
+ employee_id => $employee->id,
+ shippingdate => $doi->record->transdate,
+ )->save;
+};
+