X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FInventory.pm;h=a4c11f6a0ababcf0b843139b38f3877fa59cf431;hb=7718459cd0be728a57d7ca75dd8077316df7d730;hp=792aa2c9b9385e1693b7dea395a1e8d132c29dd5;hpb=51072516787d639c5f8df4d487155b4a66b16d6f;p=kivitendo-erp.git diff --git a/SL/Controller/Inventory.pm b/SL/Controller/Inventory.pm index 792aa2c9b..a4c11f6a0 100644 --- a/SL/Controller/Inventory.pm +++ b/SL/Controller/Inventory.pm @@ -20,6 +20,7 @@ use SL::DBUtils; use SL::Helper::Flash; use SL::Controller::Helper::ReportGenerator; use SL::Controller::Helper::GetModels; +use List::MoreUtils qw(uniq); use English qw(-no_match_vars); @@ -30,13 +31,13 @@ use Rose::Object::MakeMethods::Generic ( __PACKAGE__->run_before('_check_auth'); __PACKAGE__->run_before('_check_warehouses'); -__PACKAGE__->run_before('load_part_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed save_stocktaking) ]); -__PACKAGE__->run_before('load_unit_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed save_stocktaking) ]); -__PACKAGE__->run_before('load_wh_from_form', only => [ qw(stock_in warehouse_changed stock stocktaking save_stocktaking) ]); -__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock stocktaking save_stocktaking) ]); +__PACKAGE__->run_before('load_part_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed stocktaking_get_warn_qty_threshold save_stocktaking) ]); +__PACKAGE__->run_before('load_unit_from_form', only => [ qw(stock_in part_changed mini_stock stock stocktaking_part_changed stocktaking_get_warn_qty_threshold save_stocktaking) ]); +__PACKAGE__->run_before('load_wh_from_form', only => [ qw(stock_in warehouse_changed stock stocktaking stocktaking_get_warn_qty_threshold save_stocktaking) ]); +__PACKAGE__->run_before('load_bin_from_form', only => [ qw(stock_in stock stocktaking stocktaking_get_warn_qty_threshold save_stocktaking) ]); __PACKAGE__->run_before('set_target_from_part', only => [ qw(part_changed) ]); __PACKAGE__->run_before('mini_stock', only => [ qw(stock_in mini_stock) ]); -__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed stocktaking stocktaking_part_changed save_stocktaking) ]); +__PACKAGE__->run_before('sanitize_target', only => [ qw(stock_usage stock_in warehouse_changed part_changed stocktaking stocktaking_part_changed stocktaking_get_warn_qty_threshold save_stocktaking) ]); __PACKAGE__->run_before('set_layout'); sub action_stock_in { @@ -44,6 +45,12 @@ sub action_stock_in { $::form->{title} = t8('Stock'); + # Sometimes we want to open stock_in with a part already selected, but only + # the parts_id is passed in the url (and not also warehouse, bin and unit). + # Setting select_default_bin in the form will make sure the default warehouse + # and bin of that part will already be preselected, as normally + # set_target_from_part is only called when a part is changed. + $self->set_target_from_part if $::form->{select_default_bin}; $::request->layout->focus('#part_id_name'); my $transfer_types = WH->retrieve_transfer_types('in'); map { $_->{description} = $main::locale->text($_->{description}) } @{ $transfer_types }; @@ -58,7 +65,6 @@ sub action_stock_usage { $::form->get_lists('warehouses' => { 'key' => 'WAREHOUSES', 'bins' => 'BINS', }); - $::request->layout->use_javascript("${_}.js") for qw(kivi.PartsWarehouse); $self->setup_stock_usage_action_bar; $self->render('inventory/warehouse_usage', @@ -388,7 +394,7 @@ sub make_row_result { $row->{outcorrection}->{data} - $row->{incorrection}->{data}; $row->{averconsumed}->{data} = $row->{consumed}->{data}*30/$days ; map { $row->{$_}->{data} = $form->format_amount($myconfig,$row->{$_}->{data},2); } $self->getnumcolumns(); - $row->{partnumber}->{link} = 'controller.pl?action=Part/edit&part.id' . $partid; + $row->{partnumber}->{link} = 'controller.pl?action=Part/edit&part.id=' . $partid; } sub action_stock { @@ -411,13 +417,13 @@ sub action_stock { qty => $qty, unit => $self->unit, transfer_type => 'stock', + transfer_type_id => $::form->{transfer_type_id}, chargenumber => $::form->{chargenumber}, bestbefore => $::form->{bestbefore}, - ean => $::form->{ean}, comment => $::form->{comment}, }); 1; - } or do { $transfer_error = $EVAL_ERROR->getMessage; } + } or do { $transfer_error = $EVAL_ERROR->error; } }); if (!$transfer_error) { @@ -498,6 +504,9 @@ sub action_stocktaking { sub action_save_stocktaking { my ($self) = @_; + return $self->js->flash('error', t8('Please choose a part.'))->render() + if !$::form->{part_id}; + return $self->js->flash('error', t8('A target quantitiy has to be given'))->render() if $::form->{target_qty} eq ''; @@ -565,7 +574,7 @@ sub action_save_stocktaking { stocktaking_cutoff_date => $::form->{cutoff_date_as_date}, }); 1; - } or do { $transfer_error = $EVAL_ERROR->getMessage; } + } or do { $transfer_error = $EVAL_ERROR->error; } }); return $self->js->flash('error', $transfer_error)->render() @@ -603,6 +612,35 @@ sub action_stocktaking_journal { $self->prepare_stocktaking_report(full => 1); $self->report_generator_list_objects(report => $self->{report}, objects => $self->stocktaking_models->get); } + +sub action_stocktaking_get_warn_qty_threshold { + my ($self) = @_; + + return $_[0]->render(\ !!0, { type => 'text' }) if !$::form->{part_id}; + return $_[0]->render(\ !!0, { type => 'text' }) if $::form->{target_qty} eq ''; + return $_[0]->render(\ !!0, { type => 'text' }) if 0 == $::instance_conf->get_stocktaking_qty_threshold; + + my $target_qty = $::form->parse_amount(\%::myconfig, $::form->{target_qty}); + my $stocked_qty = _get_stocked_qty($self->part, + warehouse_id => $self->warehouse->id, + bin_id => $self->bin->id, + chargenumber => $::form->{chargenumber}, + bestbefore => $::form->{bestbefore},); + my $stocked_qty_in_form_units = $self->part->unit_obj->convert_to($stocked_qty, $self->unit); + my $qty = $target_qty - $stocked_qty_in_form_units; + $qty = abs($qty); + + my $warn; + if ($qty > $::instance_conf->get_stocktaking_qty_threshold) { + $warn = t8('The target quantity of #1 differs more than the threshold quantity of #2.', + $::form->{target_qty} . " " . $self->unit->name, + $::form->format_amount(\%::myconfig, $::instance_conf->get_stocktaking_qty_threshold, 2)); + $warn .= "\n"; + $warn .= t8('Choose "continue" if you want to use this value. Choose "cancel" otherwise.'); + } + return $_[0]->render(\ $warn, { type => 'text' }); +} + #================================================================ sub _check_auth { @@ -666,7 +704,7 @@ sub init_stocktaking_cutoff_date { my $now = DateTime->now_local; my $cutoff = DateTime->new(year => $now->year, month => 12, day => 31); if ($now->month < 1) { - $cutoff->substract(years => 1); + $cutoff->subtract(years => 1); } return $cutoff; } @@ -696,7 +734,7 @@ sub sanitize_target { } sub load_part_from_form { - $_[0]->part(SL::DB::Manager::Part->find_by_or_create(id => $::form->{part_id})); + $_[0]->part(SL::DB::Manager::Part->find_by_or_create(id => $::form->{part_id}||undef)); } sub load_unit_from_form { @@ -750,22 +788,55 @@ sub build_unit_select { sub mini_journal { my ($self) = @_; - # get last 10 transaction ids - my $query = 'SELECT trans_id, max(itime) FROM inventory GROUP BY trans_id ORDER BY max(itime) DESC LIMIT 10'; - my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query); + # We want to fetch the last 10 inventory events (inventory rows with the same trans_id) + # To prevent a Seq Scan on inventory set an index on inventory.itime + # Each event may have one (transfer_in/out) or two (transfer) inventory rows + # So fetch the last 20, group by trans_id, limit to the last 10 trans_ids, + # and then extract the inventory ids from those 10 trans_ids + # By querying Inventory->get_all via the id instead of trans_id we can make + # use of the existing index on id - my $objs; - $objs = SL::DB::Manager::Inventory->get_all(query => [ trans_id => \@ids ]) if @ids; + # inventory ids of the most recent 10 inventory trans_ids + my $query = <get_all( + query => [ id => [ \"$query" ] ], # " make emacs happy + with_objects => [ 'parts', 'trans_type', 'bin', 'bin.warehouse' ], # prevent lazy loading in template + sort_by => 'itime DESC', + ); + # remember order of trans_ids from query, for ordering hash later + my @sorted_trans_ids = uniq map { $_->trans_id } @$objs; + + # at most 2 of them belong to a transaction and the qty determines in or out. my %transactions; for (@$objs) { $transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_; $transactions{ $_->trans_id }{base} = $_; } - # and get them into order again - my @sorted = map { $transactions{$_} } @ids; + + # because the inventory transactions were built in a hash, we need to sort the + # hash by using the original sort order of the trans_ids + my @sorted = map { $transactions{$_} } @sorted_trans_ids; return \@sorted; } @@ -882,7 +953,7 @@ sub _already_counted { my %bestbefore_filter; if ($::instance_conf->get_show_bestbefore) { - %bestbefore_filter = (bestbefore => $params{bestbefore}); + %bestbefore_filter = (bestbefore => ($params{bestbefore} || undef)); } SL::DB::Manager::Stocktaking->get_all(query => [and => [parts_id => $part->id, @@ -930,6 +1001,7 @@ sub setup_stock_stocktaking_action_bar { $bar->add( action => [ t8('Save'), + checks => [ 'kivi.Inventory.check_stocktaking_qty_threshold' ], call => [ 'kivi.Inventory.save_stocktaking' ], accesskey => 'enter', ], @@ -1016,9 +1088,16 @@ and the current employee. The history is displayed via javascript. This action is called after the user selected or changed the part. +=item C + +This action checks if a warning should be shown and returns the warning text via +ajax. The warning will be shown if the given target value is greater than the +threshold given in the client configuration. + =item C This is a method to check if actions are called from stocktaking form. +This actions should contain "stocktaking" in their name. =back