Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision f179148a

Von Tamino Steinert vor mehr als 1 Jahr hinzugefügt

  • ID f179148a688e27f3016c4e74abd5a40575acb1e8
  • Vorgänger d90699ef
  • Nachfolger c08a8faf

DispositionManager: Controller hinzugefügt

Unterschiede anzeigen:

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      => 1,
87
    allow_csv_export      => 1,
88
    no_data_message       => !$noshow,
89
  );
90
  $report->set_columns(%column_defs);
91
  $report->set_column_order(@columns);
92
  $report->set_export_options(qw(list_parts));
93
  $report->set_options_from_form;
94

  
95
  unless ( $noshow ) {
96
    if ($report->{options}{output_format} =~ /^(pdf|csv)$/i) {
97
      $self->models->disable_plugin('paginated');
98
    }
99
    $self->models->finalize; # for filter laundering
100
    $self->models->set_report_generator_sort_options(
101
      report => $report, sortable_columns => \@sortable
102
    );
103
  }
104
  my $parts = $self->_get_parts(0);
105
  my $top    = $self->render('disposition_manager/list_parts', { output => 0 },
106
                             noshow => $noshow,
107
                             PARTS => $parts,
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 = SL::DB::Manager::Part->get_all(
166
    query => [
167
      'makemodels.make' => $::form->{v_id},
168
      'makemodels.sortorder' => 1,
169
    ],
170
    sort_by => 'onhand',
171
    with_objects => [ 'makemodels' ]
172
  );
173
  $self->render(
174
    'disposition_manager/_show_vendor_parts',
175
    { layout => 0 },
176
    MAKEMODEL_ITEMS => $makemodels_parts
177
  );
178
}
179

  
180
sub action_transfer_to_purchase_order {
181
  my ($self) = @_;
182
  my @error_report;
183

  
184
  my $basket_items_ids = $::form->{ids};
185
  my $vendor_items_ids = $::form->{vendor_part_ids};
186

  
187
  unless (($basket_items_ids && scalar @{ $basket_items_ids})
188
      || ( $vendor_items_ids && scalar @{ $vendor_items_ids}))
189
    {
190
    $self->js->flash('error', t8('There are no items selected'));
191
    return $self->js->render();
192
  }
193
  my $v_id =  $::form->{vendor_ids}->[0] ;
194

  
195
  my ($vendor, $employee);
196
  $vendor   = SL::DB::Manager::Vendor->find_by(id => $v_id);
197
  $employee = SL::DB::Manager::Employee->current;
198

  
199

  
200
  my @orderitem_maps = (); # part, qty, orderer_id
201
  if ($basket_items_ids && scalar @{ $basket_items_ids}) {
202
    my $basket_items = SL::DB::Manager::PurchaseBasketItem->get_all(
203
      query => [ id => $basket_items_ids ],
204
      with_objects => ['part'],
205
    );
206
    push @orderitem_maps, map {{
207
        part       => $_->part,
208
        qty        => $_->qty,
209
        orderer_id => $_->orderer_id,
210
      }} @{$basket_items};
211
  }
212
  if ($vendor_items_ids && scalar @{ $vendor_items_ids}) {
213
    my $vendor_items = SL::DB::Manager::Part->get_all(
214
      query => [ id => $vendor_items_ids ] );
215
    push @orderitem_maps, map {{
216
        part       => $_,
217
        qty        => $_->order_qty || 1,
218
        orderer_id => $employee->id,
219
      }} @{$vendor_items};
220
  }
221

  
222
  my $order = SL::DB::Order->new(
223
    vendor_id               => $vendor->id,
224
    employee_id             => $employee->id,
225
    intnotes                => $vendor->notes,
226
    salesman_id             => $employee->id,
227
    payment_id              => $vendor->payment_id,
228
    delivery_term_id        => $vendor->delivery_term_id,
229
    taxzone_id              => $vendor->taxzone_id,
230
    currency_id             => $vendor->currency_id,
231
    transdate               => DateTime->today_local
232
  );
233

  
234
  my @order_items;
235
  my $i = 0;
236
  foreach my $orderitem_map (@orderitem_maps) {
237
    $i++;
238
    my $part = $orderitem_map->{part};
239
    my $qty = $orderitem_map->{qty};
240
    my $orderer_id = $orderitem_map->{orderer_id};
241

  
242
    my $order_item = SL::DB::OrderItem->new(
243
      part                => $part,
244
      qty                 => $qty,
245
      unit                => $part->unit,
246
      description         => $part->description,
247
      price_factor_id     => $part->price_factor_id,
248
      price_factor        =>
249
        $part->price_factor_id ? $part->price_factor->factor
250
                               : '',
251
      orderer_id          => $orderer_id,
252
      position            => $i,
253
    );
254

  
255
    my $price_source  = SL::PriceSource->new(
256
      record_item => $order_item, record => $order);
257
    $order_item->sellprice(
258
      $price_source->best_price ? $price_source->best_price->price
259
                                : 0);
260
    $order_item->active_price_source(
261
      $price_source->best_price ? $price_source->best_price->source
262
                                : '');
263
    push @order_items, $order_item;
264
  }
265

  
266
  $order->assign_attributes(orderitems => \@order_items);
267

  
268
  $order->db->with_transaction( sub {
269
    $order->calculate_prices_and_taxes;
270
    $order->save;
271

  
272
    my $snumbers = "ordernumber_" . $order->ordnumber;
273
    SL::DB::History->new(
274
                      trans_id    => $order->id,
275
                      snumbers    => $snumbers,
276
                      employee_id => SL::DB::Manager::Employee->current->id,
277
                      addition    => 'SAVED',
278
                      what_done   => 'PurchaseBasket->Order',
279
                    )->save();
280
    foreach my $item(@{ $order->orderitems }){
281
      $item->parse_custom_variable_values->save;
282
      $item->{custom_variables} = \@{ $item->cvars_by_config };
283
      $item->save;
284
    }
285
    if ($basket_items_ids && scalar @{ $basket_items_ids}) {
286
      SL::DB::Manager::PurchaseBasketItem->delete_all(
287
        where => [ id => $basket_items_ids]);
288
    }
289
    return 1;
290
  }) || die "error: " . $order->db->error;
291

  
292
  $self->redirect_to(
293
    controller => 'Order',
294
    action     => 'edit',
295
    type       => 'purchase_order',
296
    vc         => 'vendor',
297
    id         => $order->id,
298
  );
299
}
300

  
301
sub action_delete_purchase_basket_items {
302

  
303
  my ($self) = @_;
304
  my @error_report;
305

  
306
  my $basket_item_ids = $::form->{ids};
307

  
308
  $main::lxdebug->dump(0, "TST: basket_items_ids", $basket_item_ids);
309

  
310
  if ($basket_item_ids && scalar @{ $basket_item_ids}) {
311
    SL::DB::Manager::PurchaseBasketItem->delete_all(
312
      where => [ id => $basket_item_ids]);
313
  } else {
314
    $self->js->flash('error', t8('There are no items selected'));
315
    return $self->js->render();
316
  }
317

  
318
  flash_later('info', t8('Selected items deleted'));
319

  
320
  $self->redirect_to(
321
    controller => 'DispositionManager',
322
    action     => 'show_basket',
323
  );
324
}
325

  
326
sub _get_parts {
327
  my ($self, $ordered) = @_;
328

  
329
  my $query = <<SQL;
330
 WITH available AS (
331
   SELECT inv.parts_id, sum(qty) as sum
332
   FROM inventory inv
333
   LEFT JOIN warehouse w ON inv.warehouse_id = w.id
334
   WHERE NOT w.invalid
335
   GROUP BY inv.parts_id
336
 
337
   UNION ALL
338
 
339
   SELECT p.id, 0 as sum
340
   FROM parts p
341
   WHERE p.id NOT IN ( SELECT distinct parts_id from inventory)
342
     AND NOT p.obsolete
343
     AND p.rop != 0
344
 )
345

  
346
 SELECT p.id
347
 FROM parts p
348
 LEFT JOIN available ava ON ava.parts_id = p.id
349
 WHERE ( ava.sum < p.rop )
350
   AND p.id NOT IN ( SELECT part_id FROM purchase_basket_items )
351
   AND NOT p.obsolete
352
 ORDER BY p.partnumber
353
SQL
354
  my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query);
355
  return unless scalar @ids;
356
  my $parts = SL::DB::Manager::Part->get_all( query => [ id => \@ids ] );
357
  my $parts_to_order = [ grep { !$_->get_open_ordered_qty } @{$parts} ];
358
  return $parts_to_order if !$ordered;
359
  my $parts_ordered = [
360
    map { $_->id } grep { $_->get_open_ordered_qty } @{$parts}
361
  ];
362
  return $parts_ordered if $ordered;
363
};
364

  
365
sub init_models {
366
  my ($self) = @_;
367
  my $parts1 = $self->_get_parts(1) || [];
368
  my @parts = @{$parts1};
369
  my $get_models =  SL::Controller::Helper::GetModels->new(
370
    controller => $self,
371
    model => 'Part',
372
    sorted => {
373
      _default => {
374
        by  => 'partnumber',
375
        dir => 1,
376
      },
377
      partnumber  => $::locale->text('Part Number'),
378
      description => $::locale->text('Description'),
379
     },
380
    query => [
381
      (id => \@parts) x !!@parts,
382
    ],
383
    paginated => {
384
      form_params => [ qw(page per_page) ],
385
      per_page    => 35,
386
    }
387
  );
388
  return $get_models;
389
}
390

  
391

  
392

  
393
sub _setup_list_action_bar {
394
  my ($self) = @_;
395
  for my $bar ($::request->layout->get('actionbar')) {
396
    $bar->add(
397
      action => [
398
        t8('Action'),
399
        submit  => [
400
          '#form', { action => "DispositionManager/add_to_purchase_basket" } ],
401
        tooltip => t8('Add to purchase basket'),
402
      ],
403
    );
404
  }
405
}
406

  
407
sub _setup_show_basket_action_bar {
408
  my ($self) = @_;
409
  for my $bar ($::request->layout->get('actionbar')) {
410
    $bar->add(
411
      action => [
412
        t8('Reload'),
413
        link => $self->url_for(
414
          controller => 'DispositionManager',
415
          action     => 'show_basket',
416
        ),
417
      ],
418
      action => [
419
        t8('Action'),
420
        call    => [ 'kivi.DispositionManager.create_order' ],
421
        tooltip => t8('Create purchase order'),
422
      ],
423
      action => [
424
        t8('Delete'),
425
        call    => [ 'kivi.DispositionManager.delete_purchase_basket_items' ],
426
        tooltip => t8('Delete selected from purchase basket'),
427
      ],
428
    );
429
  }
430
}
431
1;
432

  
433
__END__
434

  
435
=encoding utf-8
436

  
437
=head1 NAME
438

  
439
SL::Controller::DispositionManager Controller to manage purchase orders for parts
440

  
441
=head1 DESCRIPTION
442

  
443
This controller shows a list of parts using the filter minimum stock (rop).
444
From this list it is possible to put parts in a purchase basket to order.
445
It's also possible to put parts from the parts edit form in the purchase basket.
446

  
447
From the purchase basket you can create a purchase order by using the filter vendor.
448
The quantity to order will be prefilled by the value min_qty_to_order from parts or
449
makemodel(vendor_parts) or default to qty 1.
450

  
451
Tables:
452

  
453
=over 2
454

  
455
=item purchase_basket
456

  
457
=back
458

  
459
Dependencies:
460

  
461
=over 2
462

  
463
=item parts
464

  
465
=item makemodels
466

  
467

  
468
=back
469

  
470
=head1 URL ACTIONS
471

  
472
=over 4
473

  
474
=item C<action_list_parts>
475

  
476
List the parts by the filter min stock (rop) and not in an open purchase order.
477

  
478
=item C<action_add_to_purchase_basket>
479

  
480
Adds one or more parts to the purchase basket.
481

  
482
=item C<action_show_basket>
483

  
484
Shows a list with parts which are in the basket.
485
This list can be filtered by vendor. Then you can create a purchase order.
486
When filtered by vendor, a table with the parts from the vendor of the purchase basket and
487
a table with all parts from the vendor will be shown. From there you can mark
488
the parts and create an order
489

  
490
=item C<action_transfer_to_purchase_order>
491

  
492
Transfers the marked and by vendor filtered parts to a purchase order.
493
Deletes the entry in the purchase basket.
494

  
495
=back
496

  
497
=head1 BUGS
498

  
499
None yet. :)
500

  
501
=head1 AUTHOR
502

  
503
W. Hahn E<lt>wh@futureworldsearch.netE<gt>
504

  
505
=cut

Auch abrufbar als: Unified diff