Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 60e0cecf

Von Bernd Bleßmann vor 4 Monaten hinzugefügt

  • ID 60e0cecfb4e12ed8a2e11f2582f693f204a31613
  • Vorgänger a0e9a20c
  • Nachfolger d2b3f1ee

Zwischeninventur: Controller für Abgleich

Unterschiede anzeigen:

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

  
3
use strict;
4
use parent qw(SL::Controller::Base);
5

  
6
use English qw(-no_match_vars);
7
use List::Util qw(sum0);
8
use POSIX qw(strftime);
9

  
10
use SL::Controller::Helper::GetModels;
11
use SL::Controller::Helper::ReportGenerator;
12
use SL::DB::Employee;
13
use SL::DB::StockCounting;
14
use SL::DB::StockCountingItem;
15
use SL::Helper::Flash qw(flash_later);
16
use SL::Helper::Number qw(_format_total);
17
use SL::JSON;
18
use SL::Locale::String qw(t8);
19
use SL::Presenter::Tag qw(checkbox_tag);
20
use SL::ReportGenerator;
21
use SL::WH;
22

  
23
use Rose::Object::MakeMethods::Generic(
24
  #scalar => [ qw() ],
25
  'scalar --get_set_init' => [ qw(countings models) ],
26
);
27

  
28
# check permissions
29
__PACKAGE__->run_before(sub { $::auth->assert('warehouse_management'); });
30

  
31

  
32
my %sort_columns = (
33
  counting   => t8('Stock Counting'),
34
  counted_at => t8('Counted At'),
35
  qty        => t8('Qty'),
36
  part       => t8('Article'),
37
  bin        => t8('Bin'),
38
  employee   => t8('Employee'),
39
);
40

  
41

  
42
sub action_list {
43
  my ($self, %params) = @_;
44

  
45
  $self->make_filter_summary;
46
  $self->prepare_report;
47

  
48
  my $objects = $self->models->get;
49

  
50
  if ($::form->{group_counting_items}) {
51
    my $grouped_objects_by;
52
    my @grouped_objects;
53
    foreach my $object (@$objects) {
54
      my $group_object;
55
      if (!$grouped_objects_by->{$object->counting_id}->{$object->part_id}->{$object->bin_id}) {
56
        $group_object = SL::DB::StockCountingItem->new(
57
          counting => $object->counting, part => $object->part, bin => $object->bin, qty => 0);
58
        push @grouped_objects, $group_object;
59
        $grouped_objects_by->{$object->counting_id}->{$object->part_id}->{$object->bin_id} = $group_object;
60

  
61
      } else {
62
        $group_object = $grouped_objects_by->{$object->counting_id}->{$object->part_id}->{$object->bin_id}
63
      }
64

  
65
      $group_object->id($group_object->id ? ($group_object->id . ',' . $object->id) : $object->id);
66
      $group_object->qty($group_object->qty + $object->qty);
67
    }
68

  
69
    $objects = \@grouped_objects;
70
  }
71

  
72
  $self->get_stocked($objects);
73

  
74
  $self->setup_list_action_bar;
75
  $self->report_generator_list_objects(report => $self->{report}, objects => $objects);
76
}
77

  
78
sub action_reconcile {
79
  my ($self) = @_;
80

  
81
  my @transfer_errors;
82

  
83
  foreach my $selection (@{$::form->{ids}}) {
84
    my $ids               = SL::JSON::from_json($selection);
85
    my @counting_item_ids = split ',', $ids;
86
    my $counting_items    = SL::DB::Manager::StockCountingItem->get_all(query => [id => \@counting_item_ids]);
87

  
88
    my $counted_qty       = sum0 map { $_->qty } @$counting_items;
89
    my $stocked_qty       = $counting_items->[0]->part->get_stock(bin_id => $counting_items->[0]->bin_id);
90

  
91
    my $comment           = t8('correction from stock counting (counting "#1")', $counting_items->[0]->counting->name);
92

  
93
    my $transfer_qty      = $counted_qty - $stocked_qty;
94
    my $src_or_dst        = $transfer_qty < 0? 'src' : 'dst';
95
    $transfer_qty         = abs($transfer_qty);
96

  
97
    my $transfer_error;
98
    # do stock
99
    $::form->throw_on_error(sub {
100
      eval {
101
        WH->transfer({
102
          parts                   => $counting_items->[0]->part,
103
          $src_or_dst.'_bin'      => $counting_items->[0]->bin,
104
          $src_or_dst.'_wh'       => $counting_items->[0]->bin->warehouse,
105
          qty                     => $transfer_qty,
106
          unit                    => $counting_items->[0]->part->unit,
107
          transfer_type           => 'correction',
108
          comment                 => $comment,
109
        });
110
        1;
111
      } or do { $transfer_error = ref($EVAL_ERROR) eq 'SL::X::FormError' ? $EVAL_ERROR->error : $EVAL_ERROR; }
112
    });
113

  
114
    push @transfer_errors, $transfer_error if $transfer_error;
115
  }
116

  
117
  if (@transfer_errors) {
118
    flash_later('error', @transfer_errors);
119
  } else {
120
    flash_later('info', t8('successfully reconciled'));
121
  }
122

  
123
  return $self->redirect_to($::form->{callback}) if $::form->{callback};
124
}
125

  
126
sub init_models {
127
  my ($self) = @_;
128

  
129
  SL::Controller::Helper::GetModels->new(
130
    controller     => $_[0],
131
    model          => 'StockCountingItem',
132
    sorted         => \%sort_columns,
133
    disable_plugin => 'paginated',
134
    with_objects   => [ 'counting', 'employee', 'part' ],
135
  );
136
}
137

  
138
sub init_countings {
139
  SL::DB::Manager::StockCounting->get_all_sorted;
140
}
141

  
142

  
143
sub prepare_report {
144
  my ($self) = @_;
145

  
146
  my $report      = SL::ReportGenerator->new(\%::myconfig, $::form);
147
  $self->{report} = $report;
148

  
149
  my @columns = $::form->{group_counting_items} ? qw(ids counting part bin qty stocked)
150
              : qw(ids counting counted_at part bin qty stocked employee);
151

  
152
  my %column_defs = (
153
    ids        => { raw_header_data => checkbox_tag("", id => "check_all", checkall  => "[data-checkall=1]"),
154
                    align    => 'center',
155
                    raw_data => sub { $_[0]->correction_inventory_id ? '' : checkbox_tag("ids[]", value => SL::JSON::to_json($_[0]->id), "data-checkall" => 1) }   },
156
    counting   => { text => t8('Stock Counting'), sub => sub { $_[0]->counting->name }, },
157
    counted_at => { text => t8('Counted At'),     sub => sub { $_[0]->counted_at_as_timestamp }, },
158
    qty        => { text => t8('Qty'),            sub => sub { $_[0]->qty_as_number }, align => 'right' },
159
    part       => { text => t8('Article'),        sub => sub { $_[0]->part && $_[0]->part->displayable_name } },
160
    bin        => { text => t8('Bin'),            sub => sub { $_[0]->bin->full_description } },
161
    employee   => { text => t8('Employee'),       sub => sub { $_[0]->employee ? $_[0]->employee->safe_name : '---'} },
162
    stocked    => { text => t8('Stocked Qty'),    sub => sub { _format_total($_[0]->{stocked}) }, align => 'right'},
163
  );
164

  
165
  # remove columns from defs which are not in @columns
166
  foreach my $column (keys %column_defs) {
167
    delete $column_defs{$column} if !grep { $column eq $_ } @columns;
168
  }
169

  
170
  my $title        = t8('Stock Countings');
171
  $report->{title} = $title;    # for browser titlebar (title-tag)
172

  
173
  $report->set_options(
174
    controller_class      => 'StockCountingReconciliation',
175
    std_column_visibility => 1,
176
    output_format         => 'HTML',
177
    title                 => $title, # for heading
178
    allow_pdf_export      => 1,
179
    allow_csv_export      => 1,
180
  );
181

  
182
  $report->set_columns(%column_defs);
183
  $report->set_column_order(@columns);
184
  $report->set_export_options(qw(list filter group_counting_items));
185
  $report->set_options_from_form;
186

  
187
  $self->models->disable_plugin('paginated') if $report->{options}{output_format} =~ /^(pdf|csv)$/i;
188
  $self->models->add_additional_url_params(filter => $::form->{filter}, group_counting_items => $::form->{group_counting_items});
189
  $self->models->finalize;
190
  $self->models->set_report_generator_sort_options(report => $report, sortable_columns => [keys %sort_columns]);
191

  
192
  $report->set_options(
193
    raw_top_info_text    => $self->render('stock_counting_reconciliation/report_top',    { output => 0 }),
194
    raw_bottom_info_text => $self->render('stock_counting_reconciliation/report_bottom', { output => 0 }, models => $self->models),
195
    attachment_basename  => t8('stock_countings') . strftime('_%Y%m%d', localtime time),
196
  );
197
}
198

  
199
sub make_filter_summary {
200
  my ($self) = @_;
201

  
202
  my @filter_strings;
203

  
204
  push @filter_strings, t8('Group Counting Items') if $::form->{group_counting_items};
205

  
206
  my $filter = $::form->{filter} || {};
207

  
208
  my $counting = $filter->{counting_id} ? SL::DB::StockCounting->new(id => $filter->{counting_id})->load->name : '';
209

  
210
  my @filters = (
211
    [ $counting, t8('Stock Counting') ],
212
  );
213

  
214
  for (@filters) {
215
    push @filter_strings, "$_->[1]: $_->[0]" if $_->[0];
216
  }
217

  
218
  $self->{filter_summary} = join ', ', @filter_strings;
219
}
220

  
221
sub get_stocked {
222
  my ($self, $objects) = @_;
223

  
224
  $_->{stocked} = $_->part->get_stock(bin_id => $_->bin_id) for @$objects;
225
}
226

  
227
sub setup_list_action_bar {
228
  my ($self) = @_;
229

  
230
  for my $bar ($::request->layout->get('actionbar')) {
231
    $bar->add(
232
      action => [
233
        t8('Update'),
234
        submit    => [ '#filter_form', { action => 'StockCountingReconciliation/list' } ],
235
        accesskey => 'enter',
236
      ],
237
      combobox => [
238
        action => [
239
          t8('Actions'),
240
        ],
241
        action => [
242
          t8('Reconcile'),
243
          submit  => [ '#form', { action => 'StockCountingReconciliation/reconcile', callback => $self->models->get_callback } ],
244
          checks  => [ [ 'kivi.check_if_entries_selected', '[name="ids[]"]' ] ],
245
          confirm => t8('Do you really want the selected entries to be reconciled?'),
246
        ],
247
      ],
248

  
249
    );
250
  }
251
}
252

  
253

  
254
1;
menus/user/00-erp.yaml
590 590
  access: warehouse_management
591 591
  params:
592 592
    action: Inventory/stocktaking
593
- parent: warehouse
594
  id: warehouse_stock_counting_reconciliation
595
  name: Stock Counting Reconciliations
596
  order: 480
597
  access: warehouse_management
598
  params:
599
    action: StockCountingReconciliation/list
593 600
- parent: warehouse
594 601
  id: warehouse_reports
595 602
  name: Reports
templates/design40_webpages/stock_counting_reconciliation/_filter.html
1
[%- USE T8 %]
2
[%- USE L %]
3
[%- USE LxERP %]
4

  
5
<form action='controller.pl' method='post' id='filter_form'>
6
 <div class="wrapper">
7

  
8
  [% BLOCK filter_toggle_panel %]
9
   <table id='filter_table' class='tbl-horizontal'>
10
    <tbody>
11
     <tr>
12
      <th align="right">[% 'Stock Counting' | $T8 %]</th>
13
      <td>
14
       [% L.select_tag('filter.counting_id', SELF.countings,
15
                       default    => filter.counting_id,
16
                       title_key  => 'name',
17
                       value_key  => 'id',
18
                       with_empty => 1,
19
                       class      => 'wi-lightwide') %]
20
      </td>
21
     </tr>
22
     <tr>
23
      <th align="right">[% 'Group Counting Items' | $T8 %]</th>
24
      <td>
25
       [% L.checkbox_tag('group_counting_items', checked = FORM.group_counting_items, for_submit = 1) %]
26
      </td>
27
     </tr>
28
    </body>
29
   </table>
30

  
31
   [% L.hidden_tag('sort_by', FORM.sort_by) %]
32
   [% L.hidden_tag('sort_dir', FORM.sort_dir) %]
33
   [% L.hidden_tag('page', FORM.page) %]
34

  
35
   <div class="buttons">
36
    [% L.button_tag('$("#filter_form").clearForm()', LxERP.t8('Reset')) %]
37
   </div>
38

  
39
   [% END # /BLOCK filter_toggle_panel %]
40

  
41
   [% INCLUDE 'common/toggle_panel.html' %]
42

  
43
 </div>
44
</form>
templates/design40_webpages/stock_counting_reconciliation/report_bottom.html
1
[%- USE L %]
2
  [% L.paginate_controls(models=SELF.models) %]
3
 </form>
4
</div>
templates/design40_webpages/stock_counting_reconciliation/report_top.html
1
[%- PROCESS 'stock_counting_reconciliation/_filter.html' filter=SELF.models.filtered.laundered %]
2
<div class="wrapper">
3
 <form method="post" action="controller.pl" id="form">
templates/webpages/stock_counting_reconciliation/_filter.html
1
[%- USE T8 %]
2
[%- USE L %]
3
[%- USE LxERP %]
4
[%- USE HTML %]
5

  
6
<form action='controller.pl' method='post' id='filter_form'>
7
 <div class='filter_toggle'>
8
  <a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Show Filter' | $T8 %]</a>
9
  [% SELF.filter_summary | html %]
10
 </div>
11

  
12
 <div class='filter_toggle' style='display:none'>
13
  <a href='#' onClick='javascript:$(".filter_toggle").toggle()'>[% 'Hide Filter' | $T8 %]</a>
14
  <table id='filter_table'>
15
   <tr>
16
    <th align="right">[% 'Stock Counting' | $T8 %]</th>
17
    <td>
18
     [% L.select_tag('filter.counting_id', SELF.countings,
19
                     default    => filter.counting_id,
20
                     title_key  => 'name',
21
                     value_key  => 'id',
22
                     with_empty => 1,
23
                     style      => 'width: 200px') %]
24
    </td>
25
   </tr>
26
   <tr>
27
    <th align="right">[% 'Group Counting Items' | $T8 %]</th>
28
    <td>
29
     [% L.checkbox_tag('group_counting_items', checked = FORM.group_counting_items, for_submit = 1) %]
30
    </td>
31
   </tr>
32
  </table>
33

  
34
 [% L.hidden_tag('sort_by', FORM.sort_by) %]
35
 [% L.hidden_tag('sort_dir', FORM.sort_dir) %]
36
 [% L.hidden_tag('page', FORM.page) %]
37
 [% L.button_tag('$("#filter_form").clearForm()', LxERP.t8('Reset')) %]
38
 </div>
39

  
40
</form>
templates/webpages/stock_counting_reconciliation/report_bottom.html
1
[%- USE L %]
2
 [% L.paginate_controls(models=SELF.models) %]
3
</form>
templates/webpages/stock_counting_reconciliation/report_top.html
1
[%- PROCESS 'stock_counting_reconciliation/_filter.html' filter=SELF.models.filtered.laundered %]
2
<hr>
3
<form method="post" action="controller.pl" id="form">

Auch abrufbar als: Unified diff