Revision 2f9853c8
Von Tamino Steinert vor mehr als 1 Jahr hinzugefügt
SL/Controller/DispositionManager.pm | ||
---|---|---|
package SL::Controller::DispositionManager;
|
||
|
||
use strict;
|
||
|
||
use parent qw(SL::Controller::Base);
|
||
|
||
use SL::Controller::Helper::GetModels;
|
||
use SL::Controller::Helper::ReportGenerator;
|
||
use SL::DB::Part;
|
||
use SL::DB::PurchaseBasketItem;
|
||
use SL::DB::Order;
|
||
use SL::DB::OrderItem;
|
||
use SL::DB::Vendor;
|
||
use SL::PriceSource;
|
||
use SL::Locale::String qw(t8);
|
||
use SL::Helper::Flash qw(flash flash_later);
|
||
use SL::DBUtils;
|
||
|
||
use Data::Dumper;
|
||
|
||
use Rose::Object::MakeMethods::Generic (
|
||
'scalar --get_set_init' => [ qw(models) ],
|
||
);
|
||
|
||
sub action_list_parts {
|
||
my ($self) = @_;
|
||
$self->prepare_report(t8('Reorder Level List'), $::form->{noshow} ? 1 : 0 );
|
||
|
||
my $objects = $::form->{noshow} ? [] : $self->models->get;
|
||
|
||
$self->_setup_list_action_bar;
|
||
$self->report_generator_list_objects(
|
||
report => $self->{report}, objects => $objects);
|
||
}
|
||
|
||
sub prepare_report {
|
||
my ($self, $title, $noshow ) = @_;
|
||
|
||
my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
|
||
$self->{report} = $report;
|
||
|
||
my @columns = qw(
|
||
partnumber description available onhand rop ordered
|
||
);
|
||
my @visible = qw(
|
||
partnumber description available onhand rop ordered
|
||
);
|
||
my @sortable = qw(partnumber description);
|
||
|
||
my %column_defs = (
|
||
partnumber => {
|
||
sub => sub { $_[0]->partnumber },
|
||
text => t8('Part Number'),
|
||
obj_link => sub { $_[0]->presenter->link_to },
|
||
},
|
||
description => {
|
||
sub => sub { $_[0]->description },
|
||
text => t8('Part Description'),
|
||
obj_link => sub { $_[0]->presenter->link_to },
|
||
},
|
||
available => {
|
||
sub => sub { $::form->format_amount(\%::myconfig,$_[0]->onhandqty,2); },
|
||
text => t8('Available Stock'),
|
||
},
|
||
onhand => {
|
||
sub => sub { $::form->format_amount(\%::myconfig,$_[0]->stockqty,2); },
|
||
text => t8('Total Stock'),
|
||
},
|
||
rop => {
|
||
sub => sub { $::form->format_amount(\%::myconfig,$_[0]->rop,2); },
|
||
text => t8('Rop'),
|
||
},
|
||
ordered => {
|
||
sub => sub { $::form->format_amount(
|
||
\%::myconfig,$_[0]->get_open_ordered_qty,2); },
|
||
text => t8('Ordered purchase'),
|
||
},
|
||
);
|
||
|
||
map { $column_defs{$_}->{visible} = 1 } @visible;
|
||
|
||
$report->set_options(
|
||
controller_class => 'DispositionManager',
|
||
output_format => 'HTML',
|
||
title => t8($title),
|
||
allow_pdf_export => 1,
|
||
allow_csv_export => 1,
|
||
no_data_message => !$noshow,
|
||
);
|
||
$report->set_columns(%column_defs);
|
||
$report->set_column_order(@columns);
|
||
$report->set_export_options(qw(list_parts));
|
||
$report->set_options_from_form;
|
||
|
||
unless ( $noshow ) {
|
||
if ($report->{options}{output_format} =~ /^(pdf|csv)$/i) {
|
||
$self->models->disable_plugin('paginated');
|
||
}
|
||
$self->models->finalize; # for filter laundering
|
||
$self->models->set_report_generator_sort_options(
|
||
report => $report, sortable_columns => \@sortable
|
||
);
|
||
}
|
||
my $parts = $self->_get_parts(0);
|
||
my $top = $self->render('disposition_manager/list_parts', { output => 0 },
|
||
noshow => $noshow,
|
||
PARTS => $parts,
|
||
);
|
||
my $bottom = $noshow ? undef : $self->render(
|
||
'disposition_manager/reorder_level_list/report_bottom',
|
||
{ output => 0}, models => $self->models );
|
||
$report->set_options(
|
||
raw_top_info_text => $top,
|
||
raw_bottom_info_text => $bottom,
|
||
);
|
||
}
|
||
|
||
sub action_add_to_purchase_basket{
|
||
my ($self) = @_;
|
||
|
||
my $employee = SL::DB::Manager::Employee->current;
|
||
|
||
my $parts_to_add = delete($::form->{ids}) || [];
|
||
foreach my $id (@{ $parts_to_add }) {
|
||
my $part = SL::DB::Manager::Part->find_by(id => $id)
|
||
or die "Can't find part with id: $id\n";
|
||
my $needed_qty = $part->order_qty < ($part->rop - $part->onhandqty) ?
|
||
$part->rop - $part->onhandqty
|
||
: $part->order_qty;
|
||
my $basket_part = SL::DB::PurchaseBasketItem->new(
|
||
part_id => $part->id,
|
||
qty => $needed_qty,
|
||
orderer_id => $employee->id,
|
||
)->save;
|
||
}
|
||
|
||
$self->redirect_to(
|
||
controller => 'DispositionManager',
|
||
action => 'show_basket',
|
||
);
|
||
|
||
}
|
||
|
||
sub action_show_basket {
|
||
my ($self) = @_;
|
||
|
||
$::request->{layout}->add_javascripts(
|
||
'kivi.DispositionManager.js', 'kivi.Part.js'
|
||
);
|
||
my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all(
|
||
query => [ cleared => 'F' ],
|
||
with_objects => [ 'part', 'part.makemodels' ]
|
||
);
|
||
$self->_setup_show_basket_action_bar;
|
||
$self->render(
|
||
'disposition_manager/show_purchase_basket',
|
||
BASKET_ITEMS => $basket_items,
|
||
title => t8('Purchase basket'),
|
||
);
|
||
}
|
||
|
||
sub action_show_vendor_items {
|
||
my ($self) = @_;
|
||
|
||
my $makemodels_parts = SL::DB::Manager::Part->get_all(
|
||
query => [
|
||
'makemodels.make' => $::form->{v_id},
|
||
'makemodels.sortorder' => 1,
|
||
],
|
||
sort_by => 'onhand',
|
||
with_objects => [ 'makemodels' ]
|
||
);
|
||
$self->render(
|
||
'disposition_manager/_show_vendor_parts',
|
||
{ layout => 0 },
|
||
MAKEMODEL_ITEMS => $makemodels_parts
|
||
);
|
||
}
|
||
|
||
sub action_transfer_to_purchase_order {
|
||
my ($self) = @_;
|
||
my @error_report;
|
||
|
||
my $basket_items_ids = $::form->{ids};
|
||
my $vendor_items_ids = $::form->{vendor_part_ids};
|
||
|
||
unless (($basket_items_ids && scalar @{ $basket_items_ids})
|
||
|| ( $vendor_items_ids && scalar @{ $vendor_items_ids}))
|
||
{
|
||
$self->js->flash('error', t8('There are no items selected'));
|
||
return $self->js->render();
|
||
}
|
||
my $v_id = $::form->{vendor_ids}->[0] ;
|
||
|
||
my ($vendor, $employee);
|
||
$vendor = SL::DB::Manager::Vendor->find_by(id => $v_id);
|
||
$employee = SL::DB::Manager::Employee->current;
|
||
|
||
|
||
my @orderitem_maps = (); # part, qty, orderer_id
|
||
if ($basket_items_ids && scalar @{ $basket_items_ids}) {
|
||
my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all(
|
||
query => [ id => $basket_items_ids ],
|
||
with_objects => ['part'],
|
||
);
|
||
push @orderitem_maps, map {{
|
||
part => $_->part,
|
||
qty => $_->qty,
|
||
orderer_id => $_->orderer_id,
|
||
}} @{$basket_items};
|
||
}
|
||
if ($vendor_items_ids && scalar @{ $vendor_items_ids}) {
|
||
my $vendor_items = SL::DB::Manager::Part->get_all(
|
||
query => [ id => $vendor_items_ids ] );
|
||
push @orderitem_maps, map {{
|
||
part => $_,
|
||
qty => $_->order_qty || 1,
|
||
orderer_id => $employee->id,
|
||
}} @{$vendor_items};
|
||
}
|
||
|
||
my $order = SL::DB::Order->new(
|
||
vendor_id => $vendor->id,
|
||
employee_id => $employee->id,
|
||
intnotes => $vendor->notes,
|
||
salesman_id => $employee->id,
|
||
payment_id => $vendor->payment_id,
|
||
delivery_term_id => $vendor->delivery_term_id,
|
||
taxzone_id => $vendor->taxzone_id,
|
||
currency_id => $vendor->currency_id,
|
||
transdate => DateTime->today_local
|
||
);
|
||
|
||
my @order_items;
|
||
my $i = 0;
|
||
foreach my $orderitem_map (@orderitem_maps) {
|
||
$i++;
|
||
my $part = $orderitem_map->{part};
|
||
my $qty = $orderitem_map->{qty};
|
||
my $orderer_id = $orderitem_map->{orderer_id};
|
||
|
||
my $order_item = SL::DB::OrderItem->new(
|
||
part => $part,
|
||
qty => $qty,
|
||
unit => $part->unit,
|
||
description => $part->description,
|
||
price_factor_id => $part->price_factor_id,
|
||
price_factor =>
|
||
$part->price_factor_id ? $part->price_factor->factor
|
||
: '',
|
||
orderer_id => $orderer_id,
|
||
position => $i,
|
||
);
|
||
|
||
my $price_source = SL::PriceSource->new(
|
||
record_item => $order_item, record => $order);
|
||
$order_item->sellprice(
|
||
$price_source->best_price ? $price_source->best_price->price
|
||
: 0);
|
||
$order_item->active_price_source(
|
||
$price_source->best_price ? $price_source->best_price->source
|
||
: '');
|
||
push @order_items, $order_item;
|
||
}
|
||
|
||
$order->assign_attributes(orderitems => \@order_items);
|
||
|
||
$order->db->with_transaction( sub {
|
||
$order->calculate_prices_and_taxes;
|
||
$order->save;
|
||
|
||
my $snumbers = "ordernumber_" . $order->ordnumber;
|
||
SL::DB::History->new(
|
||
trans_id => $order->id,
|
||
snumbers => $snumbers,
|
||
employee_id => SL::DB::Manager::Employee->current->id,
|
||
addition => 'SAVED',
|
||
what_done => 'PurchaseBasket->Order',
|
||
)->save();
|
||
foreach my $item(@{ $order->orderitems }){
|
||
$item->parse_custom_variable_values->save;
|
||
$item->{custom_variables} = \@{ $item->cvars_by_config };
|
||
$item->save;
|
||
}
|
||
if ($basket_items_ids && scalar @{ $basket_items_ids}) {
|
||
SL::DB::Manager::PurchaseBasketItem->delete_all(
|
||
where => [ id => $basket_items_ids]);
|
||
}
|
||
return 1;
|
||
}) || die "error: " . $order->db->error;
|
||
|
||
$self->redirect_to(
|
||
controller => 'Order',
|
||
action => 'edit',
|
||
type => 'purchase_order',
|
||
vc => 'vendor',
|
||
id => $order->id,
|
||
);
|
||
}
|
||
|
||
sub action_delete_purchase_basket_items {
|
||
|
||
my ($self) = @_;
|
||
my @error_report;
|
||
|
||
my $basket_item_ids = $::form->{ids};
|
||
|
||
$main::lxdebug->dump(0, "TST: basket_items_ids", $basket_item_ids);
|
||
|
||
if ($basket_item_ids && scalar @{ $basket_item_ids}) {
|
||
SL::DB::Manager::PurchaseBasketItem->delete_all(
|
||
where => [ id => $basket_item_ids]);
|
||
} else {
|
||
$self->js->flash('error', t8('There are no items selected'));
|
||
return $self->js->render();
|
||
}
|
||
|
||
flash_later('info', t8('Selected items deleted'));
|
||
|
||
$self->redirect_to(
|
||
controller => 'DispositionManager',
|
||
action => 'show_basket',
|
||
);
|
||
}
|
||
|
||
sub _get_parts {
|
||
my ($self, $ordered) = @_;
|
||
|
||
my $query = <<SQL;
|
||
WITH available AS (
|
||
SELECT inv.parts_id, sum(qty) as sum
|
||
FROM inventory inv
|
||
LEFT JOIN warehouse w ON inv.warehouse_id = w.id
|
||
WHERE NOT w.invalid
|
||
GROUP BY inv.parts_id
|
||
|
||
UNION ALL
|
||
|
||
SELECT p.id, 0 as sum
|
||
FROM parts p
|
||
WHERE p.id NOT IN ( SELECT distinct parts_id from inventory)
|
||
AND NOT p.obsolete
|
||
AND p.rop != 0
|
||
)
|
||
|
||
SELECT p.id
|
||
FROM parts p
|
||
LEFT JOIN available ava ON ava.parts_id = p.id
|
||
WHERE ( ava.sum < p.rop )
|
||
AND p.id NOT IN ( SELECT part_id FROM purchase_basket_items )
|
||
AND NOT p.obsolete
|
||
ORDER BY p.partnumber
|
||
SQL
|
||
my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query);
|
||
return unless scalar @ids;
|
||
my $parts = SL::DB::Manager::Part->get_all( query => [ id => \@ids ] );
|
||
my $parts_to_order = [ grep { !$_->get_open_ordered_qty } @{$parts} ];
|
||
return $parts_to_order if !$ordered;
|
||
my $parts_ordered = [
|
||
map { $_->id } grep { $_->get_open_ordered_qty } @{$parts}
|
||
];
|
||
return $parts_ordered if $ordered;
|
||
};
|
||
|
||
sub init_models {
|
||
my ($self) = @_;
|
||
my $parts1 = $self->_get_parts(1) || [];
|
||
my @parts = @{$parts1};
|
||
my $get_models = SL::Controller::Helper::GetModels->new(
|
||
controller => $self,
|
||
model => 'Part',
|
||
sorted => {
|
||
_default => {
|
||
by => 'partnumber',
|
||
dir => 1,
|
||
},
|
||
partnumber => $::locale->text('Part Number'),
|
||
description => $::locale->text('Description'),
|
||
},
|
||
query => [
|
||
(id => \@parts) x !!@parts,
|
||
],
|
||
paginated => {
|
||
form_params => [ qw(page per_page) ],
|
||
per_page => 35,
|
||
}
|
||
);
|
||
return $get_models;
|
||
}
|
||
|
||
|
||
|
||
sub _setup_list_action_bar {
|
||
my ($self) = @_;
|
||
for my $bar ($::request->layout->get('actionbar')) {
|
||
$bar->add(
|
||
action => [
|
||
t8('Action'),
|
||
submit => [
|
||
'#form', { action => "DispositionManager/add_to_purchase_basket" } ],
|
||
tooltip => t8('Add to purchase basket'),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
sub _setup_show_basket_action_bar {
|
||
my ($self) = @_;
|
||
for my $bar ($::request->layout->get('actionbar')) {
|
||
$bar->add(
|
||
action => [
|
||
t8('Reload'),
|
||
link => $self->url_for(
|
||
controller => 'DispositionManager',
|
||
action => 'show_basket',
|
||
),
|
||
],
|
||
action => [
|
||
t8('Action'),
|
||
call => [ 'kivi.DispositionManager.create_order' ],
|
||
tooltip => t8('Create purchase order'),
|
||
],
|
||
action => [
|
||
t8('Delete'),
|
||
call => [ 'kivi.DispositionManager.delete_purchase_basket_items' ],
|
||
tooltip => t8('Delete selected from purchase basket'),
|
||
],
|
||
);
|
||
}
|
||
}
|
||
1;
|
||
|
||
__END__
|
||
|
||
=encoding utf-8
|
||
|
||
=head1 NAME
|
||
|
||
SL::Controller::DispositionManager Controller to manage purchase orders for parts
|
||
|
||
=head1 DESCRIPTION
|
||
|
||
This controller shows a list of parts using the filter minimum stock (rop).
|
||
From this list it is possible to put parts in a purchase basket to order.
|
||
It's also possible to put parts from the parts edit form in the purchase basket.
|
||
|
||
From the purchase basket you can create a purchase order by using the filter vendor.
|
||
The quantity to order will be prefilled by the value min_qty_to_order from parts or
|
||
makemodel(vendor_parts) or default to qty 1.
|
||
|
||
Tables:
|
||
|
||
=over 2
|
||
|
||
=item purchase_basket
|
||
|
||
=back
|
||
|
||
Dependencies:
|
||
|
||
=over 2
|
||
|
||
=item parts
|
||
|
||
=item makemodels
|
||
|
||
|
||
=back
|
||
|
||
=head1 URL ACTIONS
|
||
|
||
=over 4
|
||
|
||
=item C<action_list_parts>
|
||
|
||
List the parts by the filter min stock (rop) and not in an open purchase order.
|
||
|
||
=item C<action_add_to_purchase_basket>
|
||
|
||
Adds one or more parts to the purchase basket.
|
||
|
||
=item C<action_show_basket>
|
||
|
||
Shows a list with parts which are in the basket.
|
||
This list can be filtered by vendor. Then you can create a purchase order.
|
||
When filtered by vendor, a table with the parts from the vendor of the purchase basket and
|
||
a table with all parts from the vendor will be shown. From there you can mark
|
||
the parts and create an order
|
||
|
||
=item C<action_transfer_to_purchase_order>
|
||
|
||
Transfers the marked and by vendor filtered parts to a purchase order.
|
||
Deletes the entry in the purchase basket.
|
||
|
||
=back
|
||
|
||
=head1 BUGS
|
||
|
||
None yet. :)
|
||
|
||
=head1 AUTHOR
|
||
|
||
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
|
||
|
||
=cut
|
Auch abrufbar als: Unified diff
DispositionManager: Controller hinzugefügt