Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 20004cac

Von G. Richardson vor mehr als 5 Jahren hinzugefügt

  • ID 20004cac8f656f67e86e63fdb85f6dc083cf83d7
  • Vorgänger fdfa4918
  • Nachfolger b877d633

Part Controller - neuer Tab mit Lagerinformationen

  • Übersicht über alle Lagerbestände, wo der Artikel überall gelagert ist
    (Derzeit gibt es im Template Variabeln um Zwischensummen und
    Nachkommastellen zu kontrollieren)
  • Mini-Journal mit den letzten 10 Lagertransaktionen des Artikels

Diese Daten werden nur bei Bedarf geladen, also wenn der Benutzer auf
den neuen Tab "Lagerbewegungen/-bestände" klickt.

Außerdem gibt es Links zu diversen Lageraktionen (Einlagern, Umlagern,
Entnahme), wo der Artikel dann schon vorausgewählt ist.

Unterschiede anzeigen:

SL/Controller/Part.pm
33 33
                                  all_buchungsgruppen all_payment_terms all_warehouses
34 34
                                  parts_classification_filter
35 35
                                  all_languages all_units all_price_factors) ],
36
  'scalar'                => [ qw(warehouse bin) ],
36
  'scalar'                => [ qw(warehouse bin stock_amounts journal) ],
37 37
);
38 38

  
39 39
# safety
......
263 263
                                  history_entries => $history_entries);
264 264
}
265 265

  
266
sub action_inventory {
267
  my ($self) = @_;
268

  
269
  $::auth->assert('warehouse_contents');
270

  
271
  $self->stock_amounts($self->part->get_simple_stock_sql);
272
  $self->journal($self->part->get_mini_journal);
273

  
274
  $_[0]->render('part/_inventory_data', { layout => 0 });
275
};
276

  
266 277
sub action_update_item_totals {
267 278
  my ($self) = @_;
268 279

  
......
903 914

  
904 915
  if ( $::form->{part}{id} ) {
905 916
    return SL::DB::Part->new(id => $::form->{part}{id})->load(with => [ qw(makemodels customerprices prices translations partsgroup shop_parts shop_parts.shop) ]);
917
  } elsif ( $::form->{id} ) {
918
    return SL::DB::Part->new(id => $::form->{id})->load; # used by inventory tab
906 919
  } else {
907 920
    die "part_type missing" unless $::form->{part}{part_type};
908 921
    return SL::DB::Part->new(part_type => $::form->{part}{part_type});
SL/DB/Part.pm
3 3
use strict;
4 4

  
5 5
use Carp;
6
use List::MoreUtils qw(any);
6
use List::MoreUtils qw(any uniq);
7 7
use Rose::DB::Object::Helpers qw(as_tree);
8 8

  
9 9
use SL::Locale::String qw(t8);
......
363 363
            SUM(i.qty)                            AS qty,
364 364
            SUM(i.qty * p.lastcost)               AS stock_value,
365 365
            p.unit                                AS unit,
366
            LEAD(w.description)           OVER pt AS wh_lead,            -- to detect warehouse changes for subtotals
366
            LEAD(w.description)           OVER pt AS wh_lead,            -- to detect warehouse changes for subtotals in template
367 367
            SUM( SUM(i.qty) )             OVER pt AS run_qty,            -- running total of total qty
368 368
            SUM( SUM(i.qty) )             OVER wh AS wh_run_qty,         -- running total of warehouse qty
369 369
            SUM( SUM(i.qty * p.lastcost)) OVER pt AS run_stock_value,    -- running total of total stock_value
370 370
            SUM( SUM(i.qty * p.lastcost)) OVER wh AS wh_run_stock_value  -- running total of warehouse stock_value
371 371
       FROM inventory i
372
  LEFT JOIN parts p     ON (p.id           = i.parts_id)
373
  LEFT JOIN warehouse w ON (i.warehouse_id = w.id)
374
  LEFT JOIN bin b       ON (i.bin_id       = b.id)
372
            LEFT JOIN parts p     ON (p.id           = i.parts_id)
373
            LEFT JOIN warehouse w ON (i.warehouse_id = w.id)
374
            LEFT JOIN bin b       ON (i.bin_id       = b.id)
375 375
      WHERE parts_id = ?
376 376
   GROUP BY w.description, b.description, p.unit, i.parts_id
377 377
     HAVING SUM(qty) != 0
378
     WINDOW pt AS (PARTITION BY i.parts_id    ORDER BY w.description, b.description, p.unit),
379
            wh AS (PARTITION by w.description ORDER BY w.description, b.description, p.unit)
380
   ORDER BY w.description, b.description
378
     WINDOW pt AS (PARTITION BY i.parts_id    ORDER BY w.sortkey, b.description, p.unit),
379
            wh AS (PARTITION by w.description ORDER BY w.sortkey, b.description, p.unit)
380
   ORDER BY w.sortkey, b.description, p.unit
381 381
SQL
382 382

  
383 383
  my $stock_info = selectall_hashref_query($::form, $self->db->dbh, $query, $self->id);
384 384
  return $stock_info;
385 385
}
386 386

  
387
sub get_mini_journal {
388
  my ($self) = @_;
389

  
390
  # inventory ids of the most recent 10 inventory trans_ids
391

  
392
  # duplicate code copied from SL::Controller::Inventory mini_journal, except
393
  # for the added filter on parts_id
394

  
395
  my $parts_id = $self->id;
396
  my $query = <<"SQL";
397
with last_inventories as (
398
   select id,
399
          trans_id,
400
          itime
401
     from inventory
402
    where parts_id = $parts_id
403
 order by itime desc
404
    limit 20
405
),
406
grouped_ids as (
407
   select trans_id,
408
          array_agg(id) as ids
409
     from last_inventories
410
 group by trans_id
411
 order by max(itime)
412
     desc limit 10
413
)
414
select unnest(ids)
415
  from grouped_ids
416
 limit 20  -- so the planner knows how many ids to expect, the cte is an optimisation fence
417
SQL
418

  
419
  my $objs  = SL::DB::Manager::Inventory->get_all(
420
    query        => [ id => [ \"$query" ] ],
421
    with_objects => [ 'parts', 'trans_type', 'bin', 'bin.warehouse' ], # prevent lazy loading in template
422
    sort_by      => 'itime DESC',
423
  );
424
  # remember order of trans_ids from query, for ordering hash later
425
  my @sorted_trans_ids = uniq map { $_->trans_id } @$objs;
426

  
427
  # at most 2 of them belong to a transaction and the qty determines in or out.
428
  my %transactions;
429
  for (@$objs) {
430
    $transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_;
431
    $transactions{ $_->trans_id }{base} = $_;
432
  }
433

  
434
  # because the inventory transactions were built in a hash, we need to sort the
435
  # hash by using the original sort order of the trans_ids
436
  my @sorted = map { $transactions{$_} } @sorted_trans_ids;
437

  
438
  return \@sorted;
439
}
440

  
387 441
sub clone_and_reset_deep {
388 442
  my ($self) = @_;
389 443

  
......
612 666
description is about to change, i.e. the next line will contain numbers from a
613 667
different warehouse, so that a subtotal line can be added.
614 668

  
615
The last line will contain the qty total and the total stock value over all
616
warehouses/bins and can be used to add a line for the grand totals.
669
The last row will contain the running qty total (run_qty) and the running total
670
stock value (run_stock_value) over all warehouses/bins and can be used to add a
671
line for the grand totals.
617 672

  
618 673
=item C<items_lastcost_sum>
619 674

  
doc/changelog
12 12
  suchen soll. Ist dieses Feature eingeschaltet, so werden auch die Kunden-
13 13
  bzw. Lieferanten-Artikelnummern als Spalte in den Positionen angezeigt.
14 14

  
15
- Part Controller - neuer Tab mit Lagerinformationen - was ist wo gelagert
15 16

  
16 17
2019-08-07 - Release 3.5.4
17 18

  
locale/de/all
3016 3016
  'Stock Local/Shop'            => 'Bestand Lokal/Online',
3017 3017
  'Stock Qty for Date'          => 'Lagerbestand am',
3018 3018
  'Stock for part #1'           => 'Bestand für Artikel #1',
3019
  'Stock levels'                => 'Lagerbestände',
3019 3020
  'Stock value'                 => 'Bestandswert',
3020 3021
  'Stocked Qty'                 => 'Lagermenge',
3021 3022
  'Stocktaking'                 => 'Inventur',
templates/webpages/part/_inventory.html
1
[%- USE HTML %][%- USE L -%][%- USE P -%][%- USE LxERP -%][%- USE T8 -%]
2

  
3
[%- IF AUTH.assert('warehouse_management', 1) -%]
4
<p>
5
[% 'Actions' | $T8 %]:
6
 <span><a href="controller.pl?action=Inventory/stock_in&part_id=[% HTML.escape(SELF.part.id)%]&select_default_bin=1">[% 'Stock' | $T8 %]</a></span>
7
 <span><a href="wh.pl?trans_type=transfer&action=transfer_warehouse_selection&parts_id=[% HTML.escape(SELF.part.id) %]">[% 'Transfer' | $T8 %]</a></span>
8
 <span><a href="wh.pl?action=transfer_warehouse_selection&trans_type=removal&parts_id=[% HTML.escape(SELF.part.id) %]">[% 'Removal' | $T8 %]</a></span>
9
</p>
10
[%- END -%]
11

  
12
<div id="inventory_data">
13
</div>
14

  
15
<script type='text/javascript'>
16
$(function() {
17
  $('.tabwidget').on('tabsbeforeactivate', function(event, ui){
18
    if (ui.newPanel.attr('id') == 'inventory') {
19
      $.ajax({
20
        url: 'controller.pl?action=Part/inventory&id=[% SELF.part.id %]',
21
        success: function (html) {
22
          $("#inventory_data").html(html);
23
        },
24
      });
25
    }
26
    return 1;
27
   });
28
});
29
</script>
templates/webpages/part/_inventory_data.html
1
[%- USE HTML %][%- USE L -%][%- USE P -%][%- USE LxERP -%][%- USE T8 -%]
2

  
3
[%- SET dec = 2 %]
4
[%- SET show_warehouse_subtotals = 1 %]
5

  
6
<div id="stock_levels">
7

  
8
<h3>[% 'Stock levels' | $T8 %]</h3>
9

  
10
[%- IF SELF.stock_amounts.size %]
11
<a href="wh.pl?action=report&partnumber=[% HTML.escape(SELF.part.partnumber) %]">[% 'Stock levels' | $T8 %]</a>:
12
<table>
13
 <thead>
14
  <tr class='listheading'>
15
   <th>[% 'Warehouse'   | $T8 %]</th>
16
   <th>[% 'Bin'         | $T8 %]</th>
17
   <th>[% 'Qty'         | $T8 %]</th>
18
   <th>[% 'Unit'        | $T8 %]</th>
19
   <th>[% 'Stock value' | $T8 %]</th>
20
 </tr>
21
 </thead>
22
 <tbody>
23
 [% FOREACH stock = SELF.stock_amounts %]
24
  <tr class='listrow'>
25
   <td                >[% HTML.escape(stock.warehouse_description)  %]</td>
26
   <td                >[% IF stock.order_link %]<a target="_blank" href="[% stock.order_link %]">[% END %]
27
                       [% HTML.escape(stock.bin_description)        %]
28
                       [% IF stock.order_link %]</a>[% END %]
29
   </td>
30
   <td class='numeric'>[% LxERP.format_amount(stock.qty, dec)       %]</td>
31
   <td                >[% HTML.escape(stock.unit)                   %]</td>
32
   <td class='numeric'>[% LxERP.format_amount(stock.stock_value, 2) %]</td>
33
  </tr>
34
  [% IF show_warehouse_subtotals AND stock.wh_lead != stock.warehouse_description %]
35
  <tr class='listheading'>
36
   <th                >[% HTML.escape(stock.warehouse_description)           %]</th>
37
   <td></td>
38
   <td class='numeric bold'>[% LxERP.format_amount(stock.wh_run_qty, dec)         %]</td>
39
   <td></td>
40
   <td class='numeric bold'>[% LxERP.format_amount(stock.wh_run_stock_value, dec) %]</td>
41
  </tr>
42
  [% END %]
43
  [% IF loop.last %]
44
  <tr class='listheading'>
45
   <th>[% 'Total' | $T8 %]</th>
46
   <td></td>
47
   <td class='numeric bold'>[% LxERP.format_amount(stock.run_qty, dec)         %]</td>
48
   <td></td>
49
   <td class='numeric bold'>[% LxERP.format_amount(stock.run_stock_value, dec) %]</td>
50
  </tr>
51
  [% END %]
52
 [% END %]
53
 </tbody>
54
</table>
55
[% ELSE %]
56
  <p>[% 'No transactions yet.' | $T8 %]</p>
57
[% END %]
58
</div>
59

  
60
[% IF AUTH.assert('warehouse_management', 1) %]
61
<div>
62
<h3>[% 'Journal of Last 10 Transfers' | $T8 %]</h3>
63
<a href="wh.pl?action=journal&partnumber=[% HTML.escape(SELF.part.partnumber) %]">[% 'WHJournal' | $T8 %]</a>:
64
[%- IF SELF.journal.size %]
65
<table>
66
 <tr class='listheading'>
67
  <th>[% 'Date'           | $T8 %]</th>
68
  <th>[% 'Trans Type'     | $T8 %]</th>
69
  <th>[% 'Warehouse From' | $T8 %]</th>
70
  <th>[% 'Qty'            | $T8 %]</th>
71
  <th>[% 'Unit'           | $T8 %]</th>
72
  <th>[% 'Warehouse To'   | $T8 %]</th>
73
  <th>[% 'Charge Number'  | $T8 %]</th>
74
  <th>[% 'Comment'        | $T8 %]</th>
75
 </tr>
76
[% FOREACH row = SELF.journal %]
77
 <tr class='listrow'>
78
  <td>[% row.base.itime_as_date  %]</td>
79
  <td>[% row.base.trans_type.description | $T8 %]</td>
80
  <td>[% row.out ? row.out.bin.full_description : '-' | html %]</td>
81
  <td class='numeric'>[% row.in ? row.in.qty_as_number : LxERP.format_amount(-1 * row.out.qty, 2) %]</td>
82
  <td>[% row.base.part.unit | html %]</td>
83
  <td>[% row.in ? row.in.bin.full_description : '-' | html %]</td>
84
  <td>[% row.base.chargenumber | html %]</td>
85
  <td>[% row.base.comment | html %]</td>
86
 </tr>
87
[% END %]
88
</table>
89
[%- ELSE %]
90
<p>[% 'No transactions yet.' | $T8 %]</p>
91
[%- END %]
92
</div>
93
[% END # assert warehouse_management %]
templates/webpages/part/form.html
38 38
    [%- IF SELF.part.id  %]
39 39
    <li><a href="#price_rules">[% 'Price Rules' | $T8 %]</a></li>
40 40
    [% END %]
41
    [%- IF (AUTH.assert('warehouse_contents', 1) AND SELF.part.id AND NOT SELF.part.is_service) %]
42
    <li><a href="#inventory">[% 'Inventories' | $T8 %]</a></li>
43
    [%- END %]
41 44
    [%- IF CUSTOM_VARIABLES.size %]
42 45
    <li><a href="#custom_variables">[% 'Custom Variables' | $T8 %]</a></li>
43 46
    [%- END %]
......
75 78
     [% PROCESS 'part/_shop.html' %]
76 79
   </div>
77 80
   [%- END %]
81

  
82
   [%- IF AUTH.assert('warehouse_contents', 1) AND SELF.part.id AND NOT SELF.part.is_service %]
83
   <div id="inventory">
84
    [% PROCESS 'part/_inventory.html' %]
85
   </div>
86
   [%- END %]
87

  
78 88
   [%- END %]
79 89

  
80 90
   [%- IF CUSTOM_VARIABLES.size %]

Auch abrufbar als: Unified diff