Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 666d4cad

Von Sven Schöling vor mehr als 11 Jahren hinzugefügt

Inventory Controller und neue stock_in Maske

Alte Methode ist weiter im Code vorhanden, wird aber geplant nach und nach
durch das neue Interface ersetzt.

Benötigt Partpicker

Unterschiede anzeigen:

SL/Controller/Inventory.pm
1
package SL::Controller::Inventory;
2

  
3
use strict;
4
use warnings;
5

  
6
use parent qw(SL::Controller::Base);
7

  
8
use SL::DB::Inventory;
9
use SL::DB::Part;
10
use SL::DB::Warehouse;
11
use SL::DB::Unit;
12
use SL::WH;
13
use SL::Locale::String qw(t8);
14
use SL::ClientJS;
15
use SL::Presenter;
16
use SL::DBUtils;
17
use SL::Helper::Flash;
18

  
19
use Rose::Object::MakeMethods::Generic (
20
  'scalar --get_set_init' => [ qw(warehouses units js p) ],
21
  'scalar'                => [ qw(warehouse bin unit part) ],
22
);
23

  
24
__PACKAGE__->run_before('_check_auth');
25
__PACKAGE__->run_before('_check_warehouses');
26
__PACKAGE__->run_before('load_part_from_form',   only => [ qw(stock_in part_changed mini_stock stock) ]);
27
__PACKAGE__->run_before('load_unit_from_form',   only => [ qw(stock_in part_changed mini_stock stock) ]);
28
__PACKAGE__->run_before('load_wh_from_form',     only => [ qw(stock_in warehouse_changed stock) ]);
29
__PACKAGE__->run_before('load_bin_from_form',    only => [ qw(stock_in stock) ]);
30
__PACKAGE__->run_before('set_target_from_part',  only => [ qw(part_changed) ]);
31
__PACKAGE__->run_before('sanitize_target',       only => [ qw(stock_in warehouse_changed part_changed) ]);
32
__PACKAGE__->run_before('set_layout');
33

  
34
sub action_stock_in {
35
  my ($self) = @_;
36

  
37
  $::form->{title}   = t8('Stock');
38

  
39
  $::request->layout->focus('#part_id_name');
40
  $_[0]->render('inventory/warehouse_selection_stock', title => $::form->{title});
41
}
42

  
43
sub action_stock {
44
  my ($self) = @_;
45

  
46
  # do stock
47
  WH->transfer({
48
    parts         => $self->part,
49
    dst_bin       => $self->bin,
50
    dst_wh        => $self->warehouse,
51
    qty           => $::form->format_amount(\%::myconfig, $::form->{qty}),
52
    unit          => $self->unit,
53
    transfer_type => 'stock',
54
    chargenumber  => $::form->{chargenumber},
55
    ean           => $::form->{ean},
56
    comment       => $::form->{comment},
57
  });
58

  
59
  if ($::form->{write_default_bin}) {
60
    $self->part->bin($self->bin);
61
    $self->part->warehouse($self->warehouse);
62
    $self->part->save;
63
  }
64

  
65
  flash_later('info', t8('Transfer successful'));
66

  
67
  # redirect
68
  $self->redirect_to(
69
    action       => 'stock_in',
70
    part_id      => $self->part->id,
71
    bin_id       => $self->bin->id,
72
    warehouse_id => $self->warehouse->id,
73
  );
74
}
75

  
76
sub action_part_changed {
77
  my ($self) = @_;
78

  
79
  # no standard? ask user if he wants to write it
80
  if ($self->part->id && !$self->part->bin_id && !$self->part->warehouse_id) {
81
    $self->js->show('#write_default_bin_span');
82
  } else {
83
    $self->js->hide('#write_default_bin_span')
84
             ->removeAttr('#write_default_bin', 'checked');
85
  }
86

  
87
  $self->js
88
    ->replaceWith('#warehouse_id', $self->build_warehouse_select)
89
    ->replaceWith('#bin_id', $self->build_bin_select)
90
    ->replaceWith('#unit_id', $self->build_unit_select)
91
    ->focus('#warehouse_id')
92
    ->render($self);
93
}
94

  
95
sub action_warehouse_changed {
96
  my ($self) = @_;
97

  
98
  $self->js
99
    ->replaceWith('#bin_id', $self->build_bin_select)
100
    ->focus('#bin_id')
101
    ->render($self);
102
}
103

  
104
sub action_mini_stock {
105
  my ($self) = @_;
106

  
107
  my $stock        = $self->part->get_simple_stock;
108
  my $stock_by_bin = { map { $_->{bin_id} => $_ } @$stock };
109
  my $stock_empty  = ! grep { $_->{sum} * 1 } @$stock;
110

  
111
  $self->js
112
    ->html('#stock', $self->render('inventory/_stock', { output => 0 }, stock => $stock_by_bin, stock_empty => $stock_empty ))
113
    ->render($self);
114
}
115

  
116
sub action_last_journal {
117
  my ($self) = @_;
118

  
119
#  my $jounal = $self->journal;
120

  
121
}
122

  
123
#================================================================
124

  
125
sub _check_auth {
126
  $main::auth->assert('warehouse_management');
127
}
128

  
129
sub _check_warehouses {
130
  $_[0]->show_no_warehouses_error if !@{ $_[0]->warehouses };
131
}
132

  
133
sub init_warehouses {
134
  SL::DB::Manager::Warehouse->get_all;
135
}
136

  
137
sub init_units {
138
  SL::DB::Manager::Unit->get_all;
139
}
140

  
141
sub init_js {
142
  SL::ClientJS->new;
143
}
144

  
145
sub init_p {
146
  SL::Presenter->get;
147
}
148

  
149
sub set_target_from_part {
150
  my ($self) = @_;
151

  
152
  return if !$self->part;
153

  
154
  $self->warehouse($self->part->warehouse) if $self->part->warehouse;
155
  $self->bin(      $self->part->bin)       if $self->part->bin;
156
}
157

  
158
sub sanitize_target {
159
  my ($self) = @_;
160

  
161
  $self->warehouse(SL::DB::Manager::Warehouse->get_first) if !$self->warehouse || !$self->warehouse->id;
162
  $self->bin      ($self->warehouse->bins->[0])           if !$self->bin       || !$self->bin->id;
163
}
164

  
165
sub load_part_from_form {
166
  $_[0]->part(SL::DB::Manager::Part->find_by_or_create(id => $::form->{part_id}));
167
}
168

  
169
sub load_unit_from_form {
170
  $_[0]->unit(SL::DB::Manager::Unit->find_by_or_create(id => $::form->{unit_id}));
171
}
172

  
173
sub load_wh_from_form {
174
  $_[0]->warehouse(SL::DB::Manager::Warehouse->find_by_or_create(id => $::form->{warehouse_id}));
175
}
176

  
177
sub load_bin_from_form {
178
  $_[0]->bin(SL::DB::Manager::Bin->find_by_or_create(id => $::form->{bin_id}));
179
}
180

  
181
sub set_layout {
182
  $::request->layout->add_javascripts('client_js.js');
183
}
184

  
185
sub build_warehouse_select {
186
 $_[0]->p->select_tag('warehouse_id', $_[0]->warehouses,
187
   title_key => 'description',
188
   default   => $_[0]->warehouse->id,
189
   onchange  => 'reload_bin_selection()',
190
  )
191
}
192

  
193
sub build_bin_select {
194
  $_[0]->p->select_tag('bin_id', [ $_[0]->warehouse->bins ],
195
    title_key => 'description',
196
    default   => $_[0]->bin->id,
197
  );
198
}
199

  
200
sub build_unit_select {
201
  $_[0]->part->id
202
    ? $_[0]->p->select_tag('unit_id', $_[0]->part->available_units,
203
        title_key => 'name',
204
        default   => $_[0]->part->unit_obj->id,
205
      )
206
    : $_[0]->p->select_tag('unit_id', $_[0]->units,
207
        title_key => 'name',
208
      )
209
}
210

  
211
sub mini_journal {
212
  my ($self) = @_;
213

  
214
  # get last 10 transaction ids
215
  my $query = 'SELECT trans_id, max(itime) FROM inventory GROUP BY trans_id ORDER BY max(itime) DESC LIMIT 10';
216
  my @ids = selectall_array_query($::form, $::form->get_standard_dbh, $query);
217

  
218
  my $objs = SL::DB::Manager::Inventory->get_all(query => [ trans_id => \@ids ]);
219

  
220
  # at most 2 of them belong to a transaction and the qty determins in or out.
221
  # sort them for display
222
  my %transactions;
223
  for (@$objs) {
224
    $transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_;
225
    $transactions{ $_->trans_id }{base} = $_;
226
  }
227
  # and get them into order again
228
  my @sorted = map { $transactions{$_} } @ids;
229

  
230
  return \@sorted;
231
}
232

  
233
sub show_no_warehouse_error {
234
  my ($self) = @_;
235

  
236
  my $msg = t8('No warehouse has been created yet or the quantity of the bins is not configured yet.') . ' ';
237

  
238
  if ($::auth->check_right($::form->{login}, 'config')) { # TODO wut?
239
    $msg .= t8('You can create warehouses and bins via the menu "System -> Warehouses".');
240
  } else {
241
    $msg .= t8('Please ask your administrator to create warehouses and bins.');
242
  }
243
  $::form->show_generic_error($msg);
244
}
245

  
246
1;
SL/DB/Bin.pm
8 8

  
9 9
__PACKAGE__->meta->make_manager_class;
10 10

  
11
sub full_description {
12
  my ($self) = @_;
13

  
14
  $self->warehouse
15
    ? $self->warehouse->description . "/" . $self->description
16
    : $self->description
17
}
18

  
11 19
1;
SL/DB/Part.pm
179 179
  return $charts->{$taxzone}->{$type};
180 180
}
181 181

  
182
# this is designed to ignore chargenumbers, expiration dates and just give a list of how much <-> where
183
sub get_simple_stock {
184
  my ($self, %params) = @_;
185

  
186
  return [] unless $self->id;
187

  
188
  my $query = <<'';
189
    SELECT sum(qty), warehouse_id, bin_id FROM inventory WHERE parts_id = ?
190
    GROUP BY warehouse_id, bin_id
191

  
192
  my $stock_info = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, $self->id);
193
  [ map { bless $_, 'SL::DB::Part::SimpleStock'} @$stock_info ];
194
}
195
# helper class to have bin/warehouse accessors in stock result
196
{ package SL::DB::Part::SimpleStock;
197
  sub warehouse { require SL::DB::Warehouse; SL::DB::Manager::Warehouse->find_by_or_create(id => $_[0]->{warehouse_id}) }
198
  sub bin       { require SL::DB::Bin;       SL::DB::Manager::Bin      ->find_by_or_create(id => $_[0]->{bin_id}) }
199
}
200

  
182 201
sub long_description {
183 202
  join ' ', grep $_, map $_[0]->$_, qw(partnumber description);
184 203
}
css/lx-office-erp/main.css
54 54
  background-color: whitesmoke;
55 55
}
56 56

  
57
button:hover,
58
input[type="button"]:hover,
59
input[type="submit"]:hover {
57
button:hover:enabled,
58
input[type="button"]:hover:enabled,
59
input[type="submit"]:hover:enabled {
60 60
  border: 1px;
61 61
  background-color: lightgray;
62 62
  border-color: gray;
locale/de/all
1121 1121
  'Jan'                         => 'Jan',
1122 1122
  'January'                     => 'Januar',
1123 1123
  'Journal'                     => 'Buchungsjournal',
1124
  'Journal of Last 10 Transfers' => 'Letzte 10 Lagertransaktionen',
1124 1125
  'Jul'                         => 'Jul',
1125 1126
  'July'                        => 'Juli',
1126 1127
  'Jump to'                     => 'Springe zu',
......
1322 1323
  'No shipto selected to delete' => 'Keine Lieferadresse zum Löschen ausgewählt',
1323 1324
  'No summary account'          => 'Kein Sammelkonto',
1324 1325
  'No transaction selected!'    => 'Keine Transaktion ausgewählt',
1326
  'No transactions yet.'        => 'Bisher keine Buchungen.',
1325 1327
  'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Überweisungen ausgeführt.',
1326 1328
  'No users have been created yet.' => 'Es wurden noch keine Benutzer anleget.',
1327 1329
  'No valid number entered for pricegroup "#1".' => 'Für Preisgruppe "#1" wurde keine gültige Nummer eingegeben.',
......
1343 1345
  'Nothing has been selected for removal.' => 'Es wurde nichts f&uuml;r eine Entnahme ausgew&auml;hlt.',
1344 1346
  'Nothing has been selected for transfer.' => 'Es wurde nichts zum Umlagern ausgew&auml;hlt.',
1345 1347
  'Nothing selected!'           => 'Es wurde nichts ausgewählt!',
1348
  'Nothing stocked yet.'        => 'Noch nichts eingelagert.',
1346 1349
  'Nov'                         => 'Nov',
1347 1350
  'November'                    => 'November',
1348 1351
  'Number'                      => 'Nummer',
......
1871 1874
  'Steuersatz'                  => 'Steuersatz',
1872 1875
  'Stock'                       => 'Einlagern',
1873 1876
  'Stock Qty for Date'          => 'Lagerbestand am',
1877
  'Stock for part #1'           => 'Bestand für Artikel #1',
1874 1878
  'Stock value'                 => 'Bestandswert',
1875 1879
  'Stocked Qty'                 => 'Lagermenge',
1876 1880
  'Stop task server'            => 'Task-Server beenden',
......
2248 2252
  'Transfer out'                => 'Auslagern',
2249 2253
  'Transfer out via default'    => 'Auslagern über Standard-Lagerplatz',
2250 2254
  'Transfer qty'                => 'Umlagermenge',
2255
  'Transfer successful'         => 'Lagervorgang erfolgreich',
2251 2256
  'Translation'                 => 'Übersetzung',
2252 2257
  'Trial Balance'               => 'Summen- und Saldenliste',
2253 2258
  'Trial balance between %s and %s' => 'Summen- und Saldenlisten vom %s bis zum %s',
......
2374 2379
  'Workflow request_quotation'  => 'Workflow Preisanfrage',
2375 2380
  'Workflow sales_order'        => 'Workflow Auftrag',
2376 2381
  'Workflow sales_quotation'    => 'Workflow Angebot',
2382
  'Write bin to default bin in part?' => 'Diesen Lagerplatz als Standardlagerplatz im Artikel setzen?',
2377 2383
  'Wrong Period'                => 'Falscher Zeitraum',
2378 2384
  'Wrong tax keys recorded'     => 'Gespeicherte Steuerschlüssel sind falsch',
2379 2385
  'Wrong taxes recorded'        => 'Gespeicherte Steuern passen nicht zum Steuerschlüssel',
menu.ini
229 229

  
230 230
[Warehouse--Stock]
231 231
ACCESS=warehouse_management
232
module=wh.pl
233
action=transfer_warehouse_selection
234
trans_type=stock
232
module=controller.pl
233
action=Inventory/stock_in
235 234

  
236 235
[Warehouse--Produce Assembly]
237 236
ACCESS=warehouse_management
templates/webpages/inventory/_journal.html
1
[% USE L %]
2
[% USE HTML %]
3
[% USE LxERP %]
4
[% USE T8 %]
5
<h3>[% 'Journal of Last 10 Transfers' | $T8 %]</h3>
6

  
7
[%- IF journal.size %]
8
<table>
9
 <tr class='listheading'>
10
  <th>[% 'Date' | $T8 %]</th>
11
  <th>[% 'Trans Type' | $T8 %]</th>
12
  <th>[% 'Part' | $T8 %]</th>
13
  <th>[% 'Warehouse From' | $T8 %]</th>
14
  <th>[% 'Qty' | $T8 %]</th>
15
  <th>[% 'Unit' | $T8 %]</th>
16
  <th>[% 'Warehouse To' | $T8 %]</th>
17
  <th>[% 'Charge Number' | $T8 %]</th>
18
  <th>[% 'Comment' | $T8 %]</th>
19
 </tr>
20
[% FOREACH row = journal %]
21
 <tr class='listrow'>
22
  <td>[% row.base.itime_as_date  %]</td>
23
  <td>[% row.base.trans_type.description | $T8 %]</td>
24
  <td>[% row.base.part.long_description | html %]</td>
25
  <td>[% row.out ? row.out.bin.full_description : '-' | html %]</td>
26
  <td class='numeric'>[% row.in ? row.in.qty_as_number : LxERP.format_amount(-1 * row.out.qty, 2) %]</td>
27
  <td>[% row.base.part.unit | html %]</td>
28
  <td>[% row.in ? row.in.bin.full_description : '-' | html %]</td>
29
  <td>[% row.base.chargenumber | html %]</td>
30
  <td>[% row.base.comment | html %]</td>
31
 </tr>
32
[% END %]
33
</table>
34
[%- ELSE %]
35
<p>[% 'No transactions yet.' | $T8 %]</p>
36
[%- END %]
templates/webpages/inventory/_stock.html
1
[%- USE HTML %]
2
[%- USE LxERP %]
3
[%- USE L %]
4
[%- USE T8 %]
5
[%- IF SELF.part.id %]
6
<h3>[% LxERP.t8('Stock for part #1', SELF.part.long_description) %]</h3>
7

  
8
[%- IF stock_empty && !SELF.part.bin_id  %]
9
<p>[% 'Nothing stocked yet.' | $T8 %]</p>
10
[%- ELSE %]
11
<table>
12
  <tr class='listheading'>
13
    <th>[% 'Warehouse' | $T8 %]</th>
14
    <th>[% 'Bin' | $T8 %]</th>
15
    <th>[% 'Qty' | $T8 %]</th>
16
  </tr>
17
[%- FOREACH wh = SELF.warehouses -%]
18
[%- FOREACH bin = wh.bins -%]
19
  [%#- display any bins with stock and default bin -%]
20
  [%- SET stock__set = stock.${bin.id} -%]
21
  [%- IF stock__set.sum > 0 || SELF.part.bin_id == bin.id -%]
22
  <tr class='listrow'>
23
    <td>[% bin.warehouse.description %]</td>
24
    <td>[% bin.description %]</td>
25
    <td class='numeric'>[% LxERP.format_amount(stock__set.sum, 2) %]</td>
26
  </tr>
27
  [%- END -%]
28
[%- END -%]
29
[%- END %]
30
</table>
31
[%- END %]
32
[%- END %]
templates/webpages/inventory/warehouse_selection_stock.html
1
[%- USE T8 %]
2
[%- USE L %]
3
[%- USE HTML %]
4
[%- USE LxERP %]
5

  
6
<h1>[% title | html %]</div>
7

  
8
[%- PROCESS 'common/flash.html' %]
9

  
10
<form name="Form" method="post" action="controller.pl">
11

  
12
 <table>
13
  <tr>
14
   <th align="right" nowrap>[% 'Part' | $T8 %]</th>
15
   <td>[% L.part_picker('part_id', SELF.part) %]</td>
16
  </tr>
17

  
18
  <tr>
19
   <th align="right" nowrap>[% 'Destination warehouse' | $T8 %]</th>
20
   <td>[% L.select_tag('warehouse_id', SELF.warehouses, default=SELF.warehouse.id, title_key='description') %]
21
     [% IF SELF.warehouse.id %]
22
       [% L.select_tag('bin_id', SELF.warehouse.bins, default=SELF.bin.id, title_key='description') %]
23
      [%- ELSE %]
24
       <span id='bin_id'></span>
25
      [% END %]
26
       <span id='write_default_bin_span' style='display:none'><br>[% L.checkbox_tag('write_default_bin', label=LxERP.t8('Write bin to default bin in part?')) %]</span>
27
    </td>
28
  </tr>
29

  
30
  <tr>
31
   <th align="right" nowrap>[% 'Charge number' | $T8 %]</th>
32
   <td>[% L.input_tag('chargenumber', SELF.chargenumber, size=30) %]</td>
33
  </tr>
34

  
35
[% IF INSTANCE_CONF.get_show_bestbefore %]
36
  <tr>
37
   <th align="right" nowrap>[% 'Best Before' | $T8 %]</th>
38
   <td>[% L.date_tag('bestbefore', SELF.bestbefore) %]</td>
39
  </tr>
40
[%- END %]
41

  
42
  <tr>
43
   <th align="right" nowrap>[% 'EAN' | $T8 %]</th>
44
   <td><input name="ean" size="30" value="[% HTML.escape(ean) %]"></td>
45
  </tr>
46

  
47
  <tr>
48
   <th align="right" nowrap>[% 'Quantity' | $T8 %]</th>
49
   <td>
50
    <input name="qty" size="10" value="[% HTML.escape(LxERP.format_amount(qty)) %]">
51
[%- IF SELF.part.unit %]
52
    [% L.select_tag('unit_id', SELF.part.available_units, title_key='name', default=SELF.unit.id) %]
53
[%- ELSE %]
54
    [% L.select_tag('unit_id', SELF.units, title_key='name') %]
55
[%- END %]
56
   </td>
57
  </tr>
58

  
59
  <tr>
60
   <th align="right" nowrap>[% 'Optional comment' | $T8 %]</th>
61
   <td><input name="comment" size="60" value="[% HTML.escape(comment) %]"></td>
62
  </tr>
63
 </table>
64

  
65
 <input type="hidden" name="action" value="Inventory/dispatch">
66
 <input type="submit" id='action_stock' class="submit" name="action_stock" value="[% 'Stock' | $T8 %]" [% IF !SELF.part.id %]disabled[% END %]>
67
</form>
68

  
69
<div id='stock'>
70
  [%- PROCESS 'inventory/_stock.html' %]
71
</div>
72
<div id='journal'>
73
 [%- PROCESS 'inventory/_journal.html' journal=SELF.mini_journal %]
74
</div>
75

  
76
<script type='text/javascript'>
77
function reload_warehouse_selection () {
78
  $.post("controller.pl", { action: 'Inventory/part_changed', part_id: function(){ return $('#part_id').val() } }, kivi.eval_json_result);
79
  $.post("controller.pl", { action: 'Inventory/mini_stock', part_id: function(){ return $('#part_id').val() } }, kivi.eval_json_result);
80
}
81
function reload_bin_selection () {
82
  $.post("controller.pl", { action: 'Inventory/warehouse_changed', warehouse_id: function(){ return $('#warehouse_id').val() } }, kivi.eval_json_result);
83
}
84
$(function(){
85
  $('#part_id').change(reload_warehouse_selection);
86
  $('#part_id').change(function(){
87
    if ($('#part_id').val() > 0)
88
      $('#action_stock').removeAttr('disabled');
89
    else
90
      $('#action_stock').attr('disabled', 'disabled');
91
  });
92
  $('#warehouse_id').change(reload_bin_selection);
93
})
94
</script>

Auch abrufbar als: Unified diff