]> wagnertech.de Git - mfinanz.git/blob - SL/Controller/DispositionManager.pm
date error in mapping
[mfinanz.git] / SL / Controller / DispositionManager.pm
1 package SL::Controller::DispositionManager;
2
3 use strict;
4
5 use parent qw(SL::Controller::Base);
6
7 use SL::Controller::Helper::GetModels;
8 use SL::Controller::Helper::ReportGenerator;
9 use SL::DB::Part;
10 use SL::DB::PurchaseBasketItem;
11 use SL::DB::Order;
12 use SL::DB::OrderItem;
13 use SL::DB::Vendor;
14 use SL::PriceSource;
15 use SL::Locale::String qw(t8);
16 use SL::Helper::Flash qw(flash flash_later);
17 use SL::DBUtils;
18
19 use Data::Dumper;
20
21 use Rose::Object::MakeMethods::Generic (
22  'scalar --get_set_init' => [ qw(models) ],
23 );
24
25 sub action_list_parts {
26   my ($self) = @_;
27   $self->prepare_report(t8('Reorder Level List'), $::form->{noshow} ? 1 : 0 );
28
29   my $objects = $::form->{noshow} ? [] : $self->models->get;
30
31   $self->_setup_list_action_bar;
32   $self->report_generator_list_objects(
33     report => $self->{report}, objects => $objects);
34 }
35
36 sub prepare_report {
37   my ($self, $title, $noshow ) = @_;
38
39   my $report = SL::ReportGenerator->new(\%::myconfig, $::form);
40   $self->{report} = $report;
41
42   my @columns  = qw(
43     partnumber description available onhand rop ordered
44     );
45   my @visible  = qw(
46     partnumber description available onhand rop ordered
47     );
48   my @sortable = qw(partnumber description);
49
50   my %column_defs = (
51     partnumber  => {
52       sub      => sub { $_[0]->partnumber },
53       text     => t8('Part Number'),
54       obj_link => sub { $_[0]->presenter->link_to },
55     },
56     description => {
57       sub      => sub { $_[0]->description },
58       text     => t8('Part Description'),
59       obj_link => sub { $_[0]->presenter->link_to },
60     },
61     available   => {
62       sub  => sub { $::form->format_amount(\%::myconfig,$_[0]->onhandqty,2); },
63       text => t8('Available Stock'),
64     },
65     onhand      => {
66       sub  => sub { $::form->format_amount(\%::myconfig,$_[0]->stockqty,2); },
67       text => t8('Total Stock'),
68     },
69     rop         => {
70       sub  => sub { $::form->format_amount(\%::myconfig,$_[0]->rop,2); },
71       text => t8('Rop'),
72     },
73     ordered     => {
74       sub => sub { $::form->format_amount(
75                      \%::myconfig,$_[0]->get_open_ordered_qty,2); },
76       text => t8('Ordered purchase'),
77     },
78   );
79
80   map { $column_defs{$_}->{visible} = 1 } @visible;
81
82   $report->set_options(
83     controller_class     => 'DispositionManager',
84     output_format        => 'HTML',
85     title                => t8($title),
86     allow_pdf_export     => 0,
87     allow_csv_export     => 0,
88     allow_chart_export   => 0,
89     no_data_message      => !$noshow,
90   );
91   $report->set_columns(%column_defs);
92   $report->set_column_order(@columns);
93
94   unless ( $noshow ) {
95     if ($report->{options}{output_format} =~ /^(pdf|csv)$/i) {
96       $self->models->disable_plugin('paginated');
97     }
98     $self->models->finalize; # for filter laundering
99     $self->models->set_report_generator_sort_options(
100       report => $report, sortable_columns => \@sortable
101     );
102   }
103   my $parts = $self->_get_parts(0);
104   my $top    = $self->render('disposition_manager/list_parts', { output => 0 },
105                              noshow => $noshow,
106                              PARTS => $parts,
107                              title => t8('Short onhand Ordered'),
108                            );
109   my $bottom = $noshow ? undef : $self->render(
110     'disposition_manager/reorder_level_list/report_bottom',
111     { output => 0}, models => $self->models );
112   $report->set_options(
113     raw_top_info_text    => $top,
114     raw_bottom_info_text => $bottom,
115   );
116 }
117
118 sub action_add_to_purchase_basket{
119   my ($self) = @_;
120
121   my $employee = SL::DB::Manager::Employee->current;
122
123   my $parts_to_add = delete($::form->{ids}) || [];
124   foreach my $id (@{ $parts_to_add }) {
125     my $part = SL::DB::Manager::Part->find_by(id => $id)
126       or die "Can't find part with id: $id\n";
127     my $needed_qty = $part->order_qty < ($part->rop - $part->onhandqty) ?
128                        $part->rop - $part->onhandqty
129                      : $part->order_qty;
130     my $basket_part = SL::DB::PurchaseBasketItem->new(
131       part_id    => $part->id,
132       qty        => $needed_qty,
133       orderer_id => $employee->id,
134     )->save;
135  }
136
137  $self->redirect_to(
138    controller => 'DispositionManager',
139    action     => 'show_basket',
140  );
141
142 }
143
144 sub action_show_basket {
145   my ($self) = @_;
146
147   $::request->{layout}->add_javascripts(
148     'kivi.DispositionManager.js', 'kivi.Part.js'
149   );
150   my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all(
151     query => [ cleared => 'F' ],
152     with_objects => [ 'part', 'part.makemodels' ]
153   );
154   $self->_setup_show_basket_action_bar;
155   $self->render(
156     'disposition_manager/show_purchase_basket',
157     BASKET_ITEMS => $basket_items,
158     title => t8('Purchase basket'),
159   );
160 }
161
162 sub action_show_vendor_items {
163   my ($self) = @_;
164
165   my $makemodels_parts;
166   if ($::form->{vendor_id}) {
167     $makemodels_parts = SL::DB::Manager::Part->get_all(
168       query => [
169         'purchase_basket_item.id' => undef,
170         'makemodels.make' => $::form->{vendor_id},
171       ],
172       sort_by => 'onhand',
173       with_objects => [ 'makemodels', 'purchase_basket_item' ]
174     );
175   };
176
177   $self->render(
178     'disposition_manager/_show_vendor_parts',
179     { layout => 0 },
180     MAKEMODEL_ITEMS => $makemodels_parts
181   );
182 }
183
184 sub action_transfer_to_purchase_order {
185   my ($self) = @_;
186   my @error_report;
187
188   my $basket_item_ids = $::form->{ids};
189   my $vendor_item_ids = $::form->{vendor_part_ids};
190
191   unless (($basket_item_ids && scalar @{ $basket_item_ids})
192       || ( $vendor_item_ids && scalar @{ $vendor_item_ids}))
193     {
194     $self->js->flash('error', t8('There are no items selected'));
195     return $self->js->render();
196   }
197
198   # check for same vendor
199   my %basket_id_vendor_id_map =
200     map {$::form->{basket_ids}->[$_] => $::form->{vendor_ids}->[$_]}
201     (0..$#{$::form->{vendor_ids}});
202
203   my $vendor_id = $::form->{vendor_id_selected} || $basket_id_vendor_id_map{@{$basket_item_ids}[0]} || $basket_id_vendor_id_map{@{$basket_item_ids}[0]};
204
205   my @different_vendor_ids =
206     grep { $basket_id_vendor_id_map{$_} ne $vendor_id }
207     @{$basket_item_ids};
208   if (scalar @different_vendor_ids) {
209     $self->js->flash('error', t8('There are mulitple vendors selected'));
210     return $self->js->render();
211   }
212
213   $self->redirect_to(
214     controller => 'Order',
215     action     => 'add_from_purchase_basket',
216     type       => 'purchase_order',
217     basket_item_ids => $basket_item_ids || [],
218     vendor_item_ids => $vendor_item_ids || [],
219     vendor_id       => $vendor_id,
220   );
221 }
222
223 sub action_delete_purchase_basket_items {
224
225   my ($self) = @_;
226   my @error_report;
227
228   my $basket_item_ids = $::form->{ids};
229
230   if ($basket_item_ids && scalar @{ $basket_item_ids}) {
231     SL::DB::Manager::PurchaseBasketItem->delete_all(
232       where => [ id => $basket_item_ids]);
233   } else {
234     $self->js->flash('error', t8('There are no items selected'));
235     return $self->js->render();
236   }
237
238   flash_later('info', t8('Selected items deleted'));
239
240   $self->redirect_to(
241     controller => 'DispositionManager',
242     action     => 'show_basket',
243   );
244 }
245
246 sub _get_parts {
247   my ($self, $ordered) = @_;
248
249   my $query = <<SQL;
250  WITH available AS (
251    SELECT inv.parts_id, sum(qty) as sum
252    FROM inventory inv
253    LEFT JOIN warehouse w ON inv.warehouse_id = w.id
254    WHERE NOT w.invalid
255    GROUP BY inv.parts_id
256
257    UNION ALL
258
259    SELECT p.id, 0 as sum
260    FROM parts p
261    WHERE p.id NOT IN ( SELECT distinct parts_id from inventory)
262      AND NOT p.obsolete
263      AND p.rop != 0
264  )
265
266  SELECT p.id
267  FROM parts p
268  LEFT JOIN available ava ON ava.parts_id = p.id
269  WHERE ( ava.sum < p.rop )
270    AND p.id NOT IN ( SELECT part_id FROM purchase_basket_items )
271    AND NOT p.obsolete
272  ORDER BY p.partnumber
273 SQL
274   my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query);
275   return unless scalar @ids;
276   my $parts = SL::DB::Manager::Part->get_all( query => [ id => \@ids ] );
277   my $parts_to_order = [ grep { !$_->get_open_ordered_qty } @{$parts} ];
278   return $parts_to_order if !$ordered;
279   my $parts_ordered = [
280     map { $_->id } grep { $_->get_open_ordered_qty } @{$parts}
281   ];
282   return $parts_ordered if $ordered;
283 };
284
285 sub init_models {
286   my ($self) = @_;
287   my $parts1 = $self->_get_parts(1) || [];
288   my @parts = @{$parts1};
289   my $get_models =  SL::Controller::Helper::GetModels->new(
290     controller => $self,
291     model => 'Part',
292     sorted => {
293       _default => {
294         by  => 'partnumber',
295         dir => 1,
296       },
297       partnumber  => $::locale->text('Part Number'),
298       description => $::locale->text('Description'),
299      },
300     query => [
301       (id => \@parts) x !!@parts,
302       (id => undef) x !@parts,
303     ],
304     paginated => {
305       form_params => [ qw(page per_page) ],
306       per_page    => 35,
307     }
308   );
309   return $get_models;
310 }
311
312
313
314 sub _setup_list_action_bar {
315   my ($self) = @_;
316   for my $bar ($::request->layout->get('actionbar')) {
317     $bar->add(
318       action => [
319         t8('Purchasebasket'),
320         submit  => [
321           '#form', { action => "DispositionManager/add_to_purchase_basket" } ],
322         tooltip => t8('Add to purchase basket'),
323       ],
324     );
325   }
326 }
327
328 sub _setup_show_basket_action_bar {
329   my ($self) = @_;
330   for my $bar ($::request->layout->get('actionbar')) {
331     $bar->add(
332       action => [
333         t8('Reload'),
334         link => $self->url_for(
335           controller => 'DispositionManager',
336           action     => 'show_basket',
337         ),
338       ],
339       action => [
340         t8('Action'),
341         call    => [ 'kivi.DispositionManager.create_purchase_order' ],
342         tooltip => t8('Create purchase order'),
343       ],
344       action => [
345         t8('Delete'),
346         call    => [ 'kivi.DispositionManager.delete_purchase_basket_items' ],
347         tooltip => t8('Delete selected from purchase basket'),
348       ],
349     );
350   }
351 }
352 1;
353
354 __END__
355
356 =encoding utf-8
357
358 =head1 NAME
359
360 SL::Controller::DispositionManager Controller to manage purchase orders for parts
361
362 =head1 DESCRIPTION
363
364 This controller shows a list of parts using the filter minimum stock (rop).
365 From this list it is possible to put parts in a purchase basket to order.
366 It's also possible to put parts from the parts edit form in the purchase basket.
367
368 From the purchase basket you can create a purchase order by using the filter vendor.
369 The quantity to order will be prefilled by the value min_qty_to_order from parts or
370 makemodel(vendor_parts) or default to qty 1.
371
372 Tables:
373
374 =over 2
375
376 =item purchase_basket
377
378 =back
379
380 Dependencies:
381
382 =over 2
383
384 =item parts
385
386 =item makemodels
387
388
389 =back
390
391 =head1 URL ACTIONS
392
393 =over 4
394
395 =item C<action_list_parts>
396
397 List the parts by the filter min stock (rop) and not in an open purchase order.
398
399 =item C<action_add_to_purchase_basket>
400
401 Adds one or more parts to the purchase basket.
402
403 =item C<action_show_basket>
404
405 Shows a list with parts which are in the basket.
406 This list can be filtered by vendor. Then you can create a purchase order.
407 When filtered by vendor, a table with the parts from the vendor of the purchase basket and
408 a table with all parts from the vendor will be shown. From there you can mark
409 the parts and create an order
410
411 =item C<action_transfer_to_purchase_order>
412
413 Transfers the marked and by vendor filtered parts to a purchase order.
414 Deletes the entry in the purchase basket.
415
416 =back
417
418 =head1 BUGS
419
420 None yet. :)
421
422 =head1 AUTHOR
423
424 W. Hahn E<lt>wh@futureworldsearch.netE<gt>
425
426 =cut