X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/blobdiff_plain/b1c40d4de868ead7dc9f2a007d7d461b02318d29..1adadc3007e658f3c0723701e79f42569e71df9f:/SL/Controller/CsvImport/Inventory.pm diff --git a/SL/Controller/CsvImport/Inventory.pm b/SL/Controller/CsvImport/Inventory.pm new file mode 100644 index 000000000..b4c7c40a3 --- /dev/null +++ b/SL/Controller/CsvImport/Inventory.pm @@ -0,0 +1,401 @@ +package SL::Controller::CsvImport::Inventory; + + +use strict; + +use SL::Helper::Csv; +use SL::Helper::DateTime; + +use SL::DBUtils; +use SL::DB::Inventory; +use SL::DB::Part; +use SL::DB::Warehouse; +use SL::DB::Bin; +use SL::DB::TransferType; +use SL::DB::Employee; + +use parent qw(SL::Controller::CsvImport::Base); + + +use Rose::Object::MakeMethods::Generic +( + 'scalar --get_set_init' => [ qw(settings parts_by warehouses_by bins_by) ], +); + + +sub init_class { + my ($self) = @_; + $self->class('SL::DB::Inventory'); +} + +sub init_profile { + my ($self) = @_; + + my $profile = $self->SUPER::init_profile; + delete @{$profile}{qw(trans_id oe_id orderitems_id bestbefore trans_type_id project_id)}; + + return $profile; +} + +sub init_settings { + my ($self) = @_; + + return { map { ( $_ => $self->controller->profile->get($_) ) } qw(warehouse apply_warehouse + bin apply_bin + comment apply_comment) }; +} + +sub init_parts_by { + my ($self) = @_; + + my $all_parts = SL::DB::Manager::Part->get_all; + return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_parts } } ) } qw(id partnumber ean description) }; +} + +sub init_warehouses_by { + my ($self) = @_; + + my $all_warehouses = SL::DB::Manager::Warehouse->get_all(query => [ or => [ invalid => 0, invalid => undef ]]); + return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $all_warehouses } } ) } qw(id description) }; +} + +sub init_bins_by { + my ($self) = @_; + + my $all_bins = SL::DB::Manager::Bin->get_all(); + my $bins_by; + $bins_by->{'wh_id+id'} = { map { ( $_->warehouse_id . '+' . $_->id => $_ ) } @{ $all_bins } }; + $bins_by->{'wh_id+description'} = { map { ( $_->warehouse_id . '+' . $_->description => $_ ) } @{ $all_bins } }; + + return $bins_by; +} + +sub check_objects { + my ($self) = @_; + + $self->controller->track_progress(phase => 'building data', progress => 0); + + my $i; + my $num_data = scalar @{ $self->controller->data }; + foreach my $entry (@{ $self->controller->data }) { + $self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0; + + $self->check_warehouse($entry); + $self->check_bin($entry); + $self->check_part($entry); + $self->check_qty($entry) unless scalar @{ $entry->{errors} }; + $self->handle_comment($entry); + $self->handle_employee($entry); + $self->handle_transfer_type($entry) unless scalar @{ $entry->{errors} }; + $self->handle_shippingdate($entry); + } continue { + $i++; + } + + $self->add_info_columns(qw(warehouse bin partnumber employee target_qty)); +} + +sub setup_displayable_columns { + my ($self) = @_; + + $self->SUPER::setup_displayable_columns; + + $self->add_displayable_columns({ name => 'bin', description => $::locale->text('Bin') }, + { name => 'bin_id', description => $::locale->text('Bin (database ID)') }, + { name => 'chargenumber', description => $::locale->text('Charge number') }, + { name => 'comment', description => $::locale->text('Comment') }, + { name => 'employee_id', description => $::locale->text('Employee (database ID)') }, + { name => 'partnumber', description => $::locale->text('Part Number') }, + { name => 'parts_id', description => $::locale->text('Part (database ID)') }, + { name => 'qty', description => $::locale->text('qty (to transfer)') }, + { name => 'shippingdate', description => $::locale->text('Shipping date') }, + { name => 'target_qty', description => $::locale->text('Target Qty') }, + { name => 'warehouse', description => $::locale->text('Warehouse') }, + { name => 'warehouse_id', description => $::locale->text('Warehouse (database ID)') }, + ); +} + +sub check_warehouse { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # If warehouse from front-end is enforced for all transfers, use this, if valid. + if ($self->settings->{apply_warehouse} eq 'all') { + $object->warehouse_id(undef); + my $wh = $self->warehouses_by->{description}->{ $self->settings->{warehouse} }; + if (!$wh) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse'); + return 0; + } + + $object->warehouse_id($wh->id); + } + + # If warehouse from front-end is enforced for transfers with missing warehouse, use this, if valid. + if ( $self->settings->{apply_warehouse} eq 'missing' + && ! $object->warehouse_id + && ! $entry->{raw_data}->{warehouse} ) { + my $wh = $self->warehouses_by->{description}->{ $self->settings->{warehouse} }; + if (!$wh) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse'); + return 0; + } + + $object->warehouse_id($wh->id); + } + + # Check wether or not warehouse ID is valid. + if ($object->warehouse_id && !$self->warehouses_by->{id}->{ $object->warehouse_id }) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse'); + return 0; + } + + # Map description to ID if given. + if (!$object->warehouse_id && $entry->{raw_data}->{warehouse}) { + my $wh = $self->warehouses_by->{description}->{ $entry->{raw_data}->{warehouse} }; + if (!$wh) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid warehouse'); + return 0; + } + + $object->warehouse_id($wh->id); + } + + if ($object->warehouse_id) { + $entry->{info_data}->{warehouse} = $self->warehouses_by->{id}->{ $object->warehouse_id }->description; + } else { + push @{ $entry->{errors} }, $::locale->text('Error: Warehouse not found'); + return 0; + } + + return 1; +} + +# Check bin fior given warehouse, so check_warehouse must be called first. +sub check_bin { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # If bin from front-end is enforced for all transfers, use this, if valid. + if ($self->settings->{apply_bin} eq 'all') { + $object->bin_id(undef); + my $bin = $self->bins_by->{'wh_id+description'}->{ $object->warehouse_id . '+' . $self->settings->{bin} }; + if (!$bin) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin'); + return 0; + } + + $object->bin_id($bin->id); + } + + # If bin from front-end is enforced for transfers with missing bin, use this, if valid. + if ( $self->settings->{apply_bin} eq 'missing' + && ! $object->bin_id + && ! $entry->{raw_data}->{bin} ) { + my $bin = $self->bins_by->{'wh_id+description'}->{ $object->warehouse_id . '+' . $self->settings->{bin} }; + if (!$bin) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin'); + return 0; + } + + $object->bin_id($bin->id); + } + + # Check wether or not bin ID is valid. + if ($object->bin_id && !$self->bins_by->{'wh_id+id'}->{ $object->warehouse_id . '+' . $object->bin_id }) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin'); + return 0; + } + + # Map description to ID if given. + if (!$object->bin_id && $entry->{raw_data}->{bin}) { + my $bin = $self->bins_by->{'wh_id+description'}->{ $object->warehouse_id . '+' . $entry->{raw_data}->{bin} }; + if (!$bin) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid bin'); + return 0; + } + + $object->bin_id($bin->id); + } + + if ($object->bin_id) { + $entry->{info_data}->{bin} = $self->bins_by->{'wh_id+id'}->{ $object->warehouse_id . '+' . $object->bin_id }->description; + } else { + push @{ $entry->{errors} }, $::locale->text('Error: Bin not found'); + return 0; + } + + return 1; +} + +sub check_part { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # Check wether or non part ID is valid. + if ($object->parts_id && !$self->parts_by->{id}->{ $object->parts_id }) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid part'); + return 0; + } + + # Map number to ID if given. + if (!$object->parts_id && $entry->{raw_data}->{partnumber}) { + my $part = $self->parts_by->{partnumber}->{ $entry->{raw_data}->{partnumber} }; + if (!$part) { + push @{ $entry->{errors} }, $::locale->text('Error: Invalid part'); + return 0; + } + + $object->parts_id($part->id); + } + + if ($object->parts_id) { + $entry->{info_data}->{partnumber} = $self->parts_by->{id}->{ $object->parts_id }->partnumber; + } else { + push @{ $entry->{errors} }, $::locale->text('Error: Part not found'); + return 0; + } + + return 1; +} + +# This imports inventories when target_qty is given, transfers else. +# So we get the actual qty in stock and transfer the difference in case of +# a given target_qty +sub check_qty{ + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + if (! exists $entry->{raw_data}->{target_qty} && ! exists $entry->{raw_data}->{qty}) { + push @{ $entry->{errors} }, $::locale->text('Error: A quantity or a target quantity must be given.'); + return 0; + } + + if (exists $entry->{raw_data}->{target_qty} && exists $entry->{raw_data}->{qty}) { + push @{ $entry->{errors} }, $::locale->text('Error: a quantity and a target quantity could not be given both.'); + return 0; + } + + if (exists $entry->{raw_data}->{target_qty} && ($entry->{raw_data}->{target_qty} * 1) < 0) { + push @{ $entry->{errors} }, $::locale->text('Error: A negative target quantity is not allowed.'); + return 0; + } + + # Actual quantity is read from stock or is the result of transfers for the + # same part, warehouse and bin done before. + my $key = $object->parts_id . '+' . $object->warehouse_id . '+' . $object->bin_id; + if (!exists $self->{resulting_quantities}->{$key}) { + my $stock = $object->part->get_simple_stock; + my @stocked = grep { $_->{warehouse_id} == $object->warehouse_id && $_->{bin_id} == $object->bin_id } @$stock; + my $stocked_qty = 0; + foreach (@stocked) { + $stocked_qty += $stocked[0]->{sum} * 1; + } + $self->{resulting_quantities}->{$key} = $stocked_qty; + } + my $actual_qty = $self->{resulting_quantities}->{$key}; + + if (exists $entry->{raw_data}->{target_qty}) { + my $target_qty = $entry->{raw_data}->{target_qty} * 1; + + $object->qty($target_qty - $actual_qty); + $self->add_columns(qw(qty)); + } + + if ($object->qty == 0) { + push @{ $entry->{errors} }, $::locale->text('Error: Quantity to transfer is zero.'); + return 0; + } + + # Check if resulting quantity is below zero. + if ( ($actual_qty + $object->qty) < 0 ) { + push @{ $entry->{errors} }, $::locale->text('Error: Transfer would result in a negative target quantity.'); + return 0; + } + + $self->{resulting_quantities}->{$key} += $object->qty; + $entry->{info_data}->{target_qty} = $self->{resulting_quantities}->{$key}; + + return 1; +} + +sub handle_comment { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # If comment from front-end is enforced for all transfers, use this, if valid. + if ($self->settings->{apply_comment} eq 'all') { + $object->comment($self->settings->{comment}); + } + + # If comment from front-end is enforced for transfers with missing comment, use this, if valid. + if ($self->settings->{apply_comment} eq 'missing' && ! $object->comment) { + $object->comment($self->settings->{comment}); + } + + return; +} + +sub handle_transfer_type { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + my $transfer_type = SL::DB::Manager::TransferType->find_by(description => 'correction', + direction => ($object->qty > 0)? 'in': 'out'); + $object->trans_type($transfer_type); + + return; +} + +# ToDo: employee by name +sub handle_employee { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + # employee from front end if not given + if (!$object->employee_id) { + $object->employee_id($self->controller->{employee_id}) + } + + # employee from login if not given + if (!$object->employee_id) { + $object->employee_id(SL::DB::Manager::Employee->find_by(login => $::myconfig{login})->id); + } + + if ($object->employee_id) { + $entry->{info_data}->{employee} = $object->employee->name; + } + +} + +sub handle_shippingdate { + my ($self, $entry) = @_; + + my $object = $entry->{object}; + + if (!$object->shippingdate) { + $object->shippingdate(DateTime->today_local); + } +} + +sub save_objects { + my ($self, %params) = @_; + + my $data = $params{data} || $self->controller->data; + + foreach my $entry (@{ $data }) { + my ($trans_id) = selectrow_query($::form, $::form->get_standard_dbh, qq|SELECT nextval('id')|); + $entry->{object}->trans_id($trans_id); + } + + $self->SUPER::save_objects(%params); +} + +1;